// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "SimplygonDataConverter.h"
#include "SimplygonAssetUserData.h"
#include "SimplygonUE4UI.h"

#include "MeshUtilities.h"
#include "MaterialUtilities.h"
#include "MeshAttributes.h"
#include "Engine/StaticMesh.h"
#include "StaticMeshAttributes.h"
#include "Rendering/SkeletalMeshModel.h"
#include "MeshMergeUtilities/Private/MeshMergeHelpers.h"
#include "SimplygonContent/Classes/SimplygonSettings.h"
#include "Materials/Material.h"
#include "IMaterialBakingModule.h"
#include "MaterialBakingStructures.h"
#include "Features/IModularFeatures.h"
#include "Engine/StaticMesh.h"
#include "Modules/ModuleManager.h"

#include "Async/ParallelFor.h"
#include "HAL/PlatformFilemanager.h"
#include "Misc/ScopedSlowTask.h"
#include "Modules/ModuleManager.h"
#include "Materials/Material.h"
#include "Async/ParallelFor.h"

#include "Developer/MaterialUtilities/Public/MaterialUtilities.h"
#include "Developer/MeshMergeUtilities/Private/ProxyMaterialUtilities.h"

DEFINE_LOG_CATEGORY_STATIC(LogSimplygonDataConverter, Verbose, All);

#define SIMPLYGON_COLOR_CHANNEL "CustomVertexColors"
#define LOCTEXT_NAMESPACE "SimplygonDataConverter"
#define SG_USER_TRIANGLE_FIELD_TRIANGLEIDS "UETriangleIds"
#define SG_USER_TRIANGLE_FIELD_MATERIALIDS "UETMaterialIds"
#define SG_USER_CORNER_FIELD_VERTEXIDS "UEVertexIds"
#define SG_USER_CORNER_FIELD_CORNERIDS "UECornerIds"

struct FSkeletalMeshData
{
	TArray<SkeletalMeshImportData::FVertInfluence> Influences;
	TArray<SkeletalMeshImportData::FMeshWedge> Wedges;
	TArray<SkeletalMeshImportData::FMeshFace> Faces;
	TArray<FVector> Points;
	uint32 TexCoordCount;

	// List of vertex indices that should be ignored when re-applying morph targets to LOD
	TArray<int32> MorphTargetIgnoreList;
};

static FString GetMaterialPropertyName(EFlattenMaterialProperties prop)
{
	FString Name;

	switch (prop)
	{
	case EFlattenMaterialProperties::AmbientOcclusion:
		Name = "AmbientOcclusion";
			break;
	case EFlattenMaterialProperties::Anisotropy:
		Name = "Anisotropy";
		break;
	case EFlattenMaterialProperties::Diffuse:
		Name = "Diffuse";
		break;
	case EFlattenMaterialProperties::Emissive:
		Name = "Emissive";
		break;
	case EFlattenMaterialProperties::Metallic:
		Name = "Metallic";
		break;
	case EFlattenMaterialProperties::Normal:
		Name = "Normal";
		break;
	case EFlattenMaterialProperties::Opacity:
		Name = "Opacity";
		break;
	case EFlattenMaterialProperties::OpacityMask:
		Name = "OpacityMask";
		break;
	case EFlattenMaterialProperties::Roughness:
		Name = "Roughness";
		break;
	case EFlattenMaterialProperties::Specular:
		Name = "Specular";
		break;
	case EFlattenMaterialProperties::SubSurface:
		Name = "SubSurface";
		break;
	case EFlattenMaterialProperties::Tangent:
		Name = "Tangent";
		break;
	default:
		Name = "Unsupported material property";
		break;
	}

	return Name;
}

// Add to scene - UStaticMesh
void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	UStaticMesh* StaticMesh,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	const int32 LODIndex = 0;

	FMeshDescription* MeshDescription = new FMeshDescription();
	FStaticMeshAttributes(*MeshDescription).Register();
	FMeshMergeHelpers::RetrieveMesh(StaticMesh, LODIndex, *MeshDescription);

	FString MeshDescriptionId = FString::Printf(TEXT("%d"), StaticMesh->GetUniqueID());
	OutMeshMaterialInfo.MeshesToRelease.Add(MeshDescriptionId);

	TArray<FSectionInfo> Sections;
	FMeshMergeHelpers::ExtractSections(StaticMesh, LODIndex, Sections);

	Simplygon::spGeometryData GeometryData = CreateGeometryData(SDK, FlattenMaterialSettings, MeshDescription, MeshDescriptionId, Sections, OutMeshMaterialInfo);

	// Create Simplygon mesh scene node
	Simplygon::spSceneMesh MeshNode = SDK->CreateSceneMesh();
	MeshNode->SetGeometry(GeometryData);
	MeshNode->SetName(TCHAR_TO_ANSI(*StaticMesh->GetFName().ToString()));
	Scene->GetRootNode()->AddChild(MeshNode);
}

// Add to scene - USkeletalMesh
void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	USkeletalMesh* SkeletalMesh,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	// Create Geometry Data from Skeletal Mesh
	Simplygon::spGeometryData GeometryData = CreateGeometryData(SDK, Scene, FlattenMaterialSettings, SkeletalMesh, OutMeshMaterialInfo);

	// Add Geometry Data to Simplygon scene
	AddToScene(SDK, Scene, GeometryData, SkeletalMesh->GetName());
}

// Add to scene - UStaticMeshComponent
void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	UStaticMeshComponent* StaticMeshComponent,
	FSimplygonMeshMaterialInfo& MeshMaterialInfo)
{
	FMeshDescription* MeshDescription = new FMeshDescription();
	FStaticMeshAttributes(*MeshDescription).Register();
	FMeshMergeHelpers::RetrieveMesh(StaticMeshComponent, 0, *MeshDescription, true);

	FString MeshDescriptionId = FString::Printf(TEXT("%d"), StaticMeshComponent->GetStaticMesh()->GetUniqueID());
	MeshMaterialInfo.MeshesToRelease.Add(MeshDescriptionId);

	TArray<FSectionInfo> Sections;
	// Extract sections for given LOD index from the mesh
	FMeshMergeHelpers::ExtractSections(StaticMeshComponent, 0, Sections);

	// If the component is an ISMC then we need to duplicate the vertex data
	if (StaticMeshComponent->IsA<UInstancedStaticMeshComponent>())
	{
		const UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast<UInstancedStaticMeshComponent>(StaticMeshComponent);
		FMeshMergeHelpers::ExpandInstances(InstancedStaticMeshComponent, *MeshDescription, Sections);
	}

	Simplygon::spGeometryData GeometryData = CreateGeometryData(SDK, FlattenMaterialSettings, MeshDescription, MeshDescriptionId, Sections, MeshMaterialInfo);

	// Create Simplygon mesh scene node
	Simplygon::spSceneMesh MeshNode = SDK->CreateSceneMesh();
	MeshNode->SetGeometry(GeometryData);
	MeshNode->SetName(TCHAR_TO_ANSI(*StaticMeshComponent->GetFName().ToString()));
	Scene->GetRootNode()->AddChild(MeshNode);

	// Add geometry data to selection set
	{
		auto SimplygonUserData = StaticMeshComponent->GetAssetUserData<USimplygonAssetUserData>();
		bool bIsClippingComponent = SimplygonUserData ? (SimplygonUserData->MetaTag == ESimplygonMetaTagType::ClippingGeometry) : false;
		bool bIsOccluderComponent = SimplygonUserData ? (SimplygonUserData->MetaTag == ESimplygonMetaTagType::Occluder) : false;
		bool bIsGeometryCameraPath = SimplygonUserData ? (SimplygonUserData->MetaTag == ESimplygonMetaTagType::GeometryCameraPath) : false;

		if (bIsClippingComponent)
		{
			Simplygon::spSelectionSet ClippingSet = FindOrCreateSelectionSet(SDK, Scene, SG_CLIPPING_GEOMETRY_SELECTION_SET_NAME);
			ClippingSet->AddItem(MeshNode->GetNodeGUID());
		}
		else if (bIsOccluderComponent)
		{
			Simplygon::spSelectionSet OccluderSet = FindOrCreateSelectionSet(SDK, Scene, SG_OCCLUDER_GEOMETRY_SELECTION_SET_NAME);
			OccluderSet->AddItem(MeshNode->GetNodeGUID());
		}
		else if (bIsGeometryCameraPath)
		{
			Simplygon::spSelectionSet CameraSet = FindOrCreateSelectionSet(SDK, Scene, SG_VISIBILITY_SELECTION_SET_NAME);
			CameraSet->AddItem(MeshNode->GetNodeGUID());
		}
		else
		{
			// With 9.1 ProcessSelectionSet is automatically created within the Simplygon API from any mesh that does not belong to any other set.
		}
	}
}

// Add to scene - Materials
void FSimplygonDataConverter::AddToScene(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, FSimplygonMeshMaterialInfo& MeshMaterialInfo)
{
	FScopedSlowTask Progress(1.0, LOCTEXT("AddMaterialsToScene", "Adding materials to Simplygon scene (flattening materials)..."), true);
	Progress.MakeDialog();

	IMaterialBakingModule& MaterialBakingModule = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");

	TArray<FMaterialData> GlobalMaterialSettings;
	TArray<FMeshData*> MeshSettingPtrs;
	TArray<FMaterialData*> MaterialSettingPtrs;

	for (const FMeshMaterialSectionId& Id : MeshMaterialInfo.MaterialOutputOrder)
	{
		MeshSettingPtrs.Add(&MeshMaterialInfo.BakeMaterialMeshSettings[Id]);

		GlobalMaterialSettings.Add(MeshMaterialInfo.BakeMaterialMaterialSettings[Id]);
		MaterialSettingPtrs.Add(&MeshMaterialInfo.BakeMaterialMaterialSettings[Id]);
	}

	Progress.EnterProgressFrame(1.0f);
	TArray<FBakeOutput> BakeOutputs;
	MaterialBakingModule.BakeMaterials(MaterialSettingPtrs, MeshSettingPtrs, BakeOutputs);

	TArray<FFlattenMaterial> FlattenedMaterialArray;
	ConvertMaterialBakeOutput(BakeOutputs, GlobalMaterialSettings, FlattenedMaterialArray);

	AddSimplygonMaterials(SDK, Scene, FlattenedMaterialArray, true);

	// Release data
	TArray<FString> ReleasedMeshes;
	for (auto& IdToMeshSettings : MeshMaterialInfo.BakeMaterialMeshSettings)
	{
		FString* MeshId = MeshMaterialInfo.MeshesToRelease.FindByPredicate([&IdToMeshSettings](const FString& MatchMeshId) {
			return IdToMeshSettings.Key.Contains(MatchMeshId);
		});

		if (!MeshId)
			continue;

		bool bMeshAlreadyReleased = ReleasedMeshes.Contains(*MeshId);
		if (bMeshAlreadyReleased)
		{
			IdToMeshSettings.Value.RawMeshDescription = nullptr;
		}
		else
		{
			if (IdToMeshSettings.Value.RawMeshDescription != nullptr)
			{
				delete IdToMeshSettings.Value.RawMeshDescription;
				IdToMeshSettings.Value.RawMeshDescription = nullptr;
				ReleasedMeshes.Add(*MeshId);
			}
		}
	}
}

void FSimplygonDataConverter::AddToScene(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, Simplygon::spPipeline Pipeline, const TArray<FPlane>& ClippingPlanes)
{
	bool bHasClippingPlanes = ClippingPlanes.Num() > 0 && FSimplygonDataConverter::IsClippingPlaneEnabledInPipeline(Pipeline);

	if (bHasClippingPlanes)
	{
		Simplygon::spSelectionSet ClippingPlaneSet = FindOrCreateSelectionSet(SDK, Scene, SG_CLIPPING_PLANES_SELECTION_SET_NAME);

		for (auto& Plane : ClippingPlanes)
		{
			Simplygon::spScenePlane TempPlane = SDK->CreateScenePlane();
			FVector Normal = Plane.GetSafeNormal();
			FVector Position(Plane.GetComponentForAxis(EAxis::X) + Plane.W, Plane.GetComponentForAxis(EAxis::Y) + Plane.W, Plane.GetComponentForAxis(EAxis::Z) + Plane.W);
			Normal = FSimplygonDataConverter::VectorToSimplygon(Normal);
			Position = FSimplygonDataConverter::VectorToSimplygon(Position);
			float Pos[3] = {float(Position.X), float(Position.Y), float(Position.Z)};
			float Nor[3] = {float(Normal.X), float(Normal.Y), float(Normal.Z)};

			TempPlane->SetPosition(&Pos[0]);
			TempPlane->SetNormal(&Nor[0]);

			Scene->GetRootNode()->AddChild(TempPlane);
			ClippingPlaneSet->AddItem(TempPlane->GetNodeGUID());
		}

		if (Pipeline->IsA("IRemeshingPipeline"))
		{
			FSimplygonDataConverter::SetupClippingPlanesFromPipelineSettings<Simplygon::spRemeshingPipeline>(Pipeline);
		}
		else if (Pipeline->IsA("IRemeshingLegacyPipeline"))
		{
			//FSimplygonDataConverter::SetupClippingPlanesFromPipelineSettings<Simplygon::spRemeshingLegacyPipeline>(Pipeline);
		}
		else if (Pipeline->IsA("IAggregationPipeline"))
		{
			FSimplygonDataConverter::SetupClippingPlanesFromPipelineSettings<Simplygon::spAggregationPipeline>(Pipeline);
		}
	}
	else
		FSimplygonDataConverter::DisableClippingPlaneInPipeline(Pipeline);
}

void FSimplygonDataConverter::AddToScene(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, Simplygon::spPipeline Pipeline, const TArray<FVector>& VisibilitySamples)
{
	bool bSampleNavmeshVisibility = VisibilitySamples.Num() > 0;

	if (bSampleNavmeshVisibility)
	{
		const int32 Resolution = 512;
		Simplygon::spSelectionSet VisibilitySet = FindOrCreateSelectionSet(SDK, Scene, SG_VISIBILITY_SELECTION_SET_NAME);

		Simplygon::spSceneCamera SceneCamera = SDK->CreateSceneCamera();
		SceneCamera->SetCameraType(Simplygon::ECameraType::Omnidirectional);
		SceneCamera->SetFieldOfView(FMath::DegreesToRadians(90));
		SceneCamera->SetPixelFieldOfView(FMath::DegreesToRadians(90) / Resolution);

		// Get the camera position
		Simplygon::spRealArray CameraPositions = SceneCamera->GetCameraPositions();

		// Set the tuple count to 1
		CameraPositions->SetTupleCount(VisibilitySamples.Num());

		FVector TransformedSample;
		for (auto SampleIndex = 0; SampleIndex < VisibilitySamples.Num(); ++SampleIndex)
		{
			TransformedSample = FSimplygonDataConverter::VectorToSimplygon(VisibilitySamples[SampleIndex]);
			float TempPoint[3] = {float(TransformedSample.X), float(TransformedSample.Y), float(TransformedSample.Z)};
			CameraPositions->SetTuple(SampleIndex, &TempPoint[0]);
		}

		if (SceneCamera->ValidateCamera())
		{
			Scene->GetRootNode()->AddChild(SceneCamera);
			VisibilitySet->AddItem(SceneCamera->GetNodeGUID());
		}

		if (Pipeline->IsA("IRemeshingPipeline"))
		{
			//does not support visibility for now
			FSimplygonDataConverter::SetupVisibilityCameraSetFromPipelineSettings<Simplygon::spRemeshingPipeline>(Pipeline);
		}
		else if (Pipeline->IsA("IAggregationPipeline"))
		{
			FSimplygonDataConverter::SetupVisibilityCameraSetFromPipelineSettings<Simplygon::spAggregationPipeline>(Pipeline);
		}
	}
}

void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	Simplygon::spPipeline Pipeline,
	const FMeshDescription* CullingGeometry)
{

	check(CullingGeometry != nullptr);
	TMap<FName, int32> MaterialMap;
	TArray<FBox2D> TexCoordsBounds; // Init empty array to skip bounds
	TArray<FVector2D> TexCoords;	// Init empty array to skip bounds

	auto CullingGeoemtryData = CreateGeometryFromMeshDescription(SDK, *CullingGeometry, MaterialMap, TexCoordsBounds, TexCoords, false);

	Simplygon::spSceneMesh MeshNode = SDK->CreateSceneMesh();
	MeshNode->SetGeometry(CullingGeoemtryData);
	Scene->GetRootNode()->AddChild(MeshNode);

	auto ClippingGeoemtrySet = FindOrCreateSelectionSet(SDK, Scene, SG_CLIPPING_GEOMETRY_SELECTION_SET_NAME);
	ClippingGeoemtrySet->AddItem(MeshNode->GetNodeGUID().Data());
}

void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	Simplygon::spGeometryData GeometryData,
	const FString& NodeName)
{
	Simplygon::spSceneMesh MeshNode = SDK->CreateSceneMesh();
	MeshNode->SetGeometry(GeometryData);
	if (NodeName.IsEmpty())
		MeshNode->SetName(TCHAR_TO_ANSI(TEXT("MeshNode")));
	else
		MeshNode->SetName(TCHAR_TO_ANSI(*NodeName));
	Scene->GetRootNode()->AddChild(MeshNode);
}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryData(
	Simplygon::ISimplygon* API,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	const FMeshDescription* MeshDescription,
	const FString& MeshDescriptionId,
	const TArray<FSectionInfo>& Sections,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	auto MaterialInfo = OutMeshMaterialInfo.Add(FlattenMaterialSettings, Sections, MeshDescription, MeshDescriptionId);

	TArray<FBox2D> TexCoordsBounds; // Init empty array to skip bounds
	TArray<FVector2D> TexCoords;	// Init empty array to skip bounds
	Simplygon::spGeometryData Geometry = FSimplygonDataConverter::CreateGeometryFromMeshDescription(API, *MeshDescription, MaterialInfo.SlotNameToMaterialId, TexCoordsBounds, TexCoords, false);

	if (OutMeshMaterialInfo.bBakeMaterials)
	{
		// Generate flatten material texture coordinates
		TArray<FVector2D> CustomTextureCoordinates;
		FSimplygonDataConverter::PackChartsUsingChartAggregator(
			API, Geometry, FlattenMaterialSettings, SG_MATERIALBAKING_INDEX, MaterialInfo, CustomTextureCoordinates);

		// Copy custom texture coordinates to mesh settings
		for (const FString& MeshSectionId : MaterialInfo.ComponentMeshMaterialKeys)
		{
			FMeshData* MeshSettings = OutMeshMaterialInfo.BakeMaterialMeshSettings.Find(MeshSectionId);

			if (MeshSettings)
			{
				MeshSettings->CustomTextureCoordinates = CustomTextureCoordinates;
			}
		}
	}

	return Geometry;
}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryData(
	Simplygon::ISimplygon* API,
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	USkeletalMesh* SkeletalMesh,
	const TArray<FSectionInfo>& /*Sections*/,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	return CreateGeometryData(API, Scene, FlattenMaterialSettings, SkeletalMesh, OutMeshMaterialInfo);
}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryData(
	Simplygon::ISimplygon* API,
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	USkeletalMesh* SkeletalMesh,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	TArray<FSectionInfo> Sections;
	ExtractSections(SkeletalMesh, 0, Sections);

	FMeshDescription* MeshDescriptionForFlatten = new FMeshDescription();
	FStaticMeshAttributes(*MeshDescriptionForFlatten).Register();
	ExtractMeshDescription(SkeletalMesh, 0, MeshDescriptionForFlatten, true);

	FString MeshId = FString::Printf(TEXT("%d"), SkeletalMesh->GetUniqueID());
	OutMeshMaterialInfo.MeshesToRelease.Add(MeshId);

	auto MaterialInfoResult = OutMeshMaterialInfo.Add(FlattenMaterialSettings, Sections, MeshDescriptionForFlatten, MeshId);

	FSkeletalMeshModel* SkeletalMeshResource = SkeletalMesh->GetImportedModel();
	check(SkeletalMeshResource);
	FSkeletalMeshLODModel* BaseMeshLODModel = &SkeletalMeshResource->LODModels[0];
	const TArray<FMatrix>& BoneMatrices = TArray<FMatrix>(); //BakePoseToLOD(LODIndex);

	// Create bone hierarchy for simplygon.
	TArray<Simplygon::rid> BoneTableIDs;
	FSimplygonDataConverter::CreateSkeletalHierarchy(API, Scene, SkeletalMesh->GetRefSkeleton(), BoneTableIDs);

	// Create a new scene mesh object
	Simplygon::spGeometryData Geometry = FSimplygonDataConverter::CreateGeometryFromSkeletalLODModel(
		API,
		*BaseMeshLODModel,
		BoneTableIDs,
		BoneMatrices,
		MaterialInfoResult.SectionIndexToMaterialId,
		TArray<FBox2D>(),
		TArray<FVector2D>());

	if (OutMeshMaterialInfo.bBakeMaterials)
	{
		//Generate flatten material texture coordinates
		TArray<FVector2D> CustomTextureCoordinates;

		FSimplygonDataConverter::PackChartsUsingChartAggregator(
			API, Geometry, FlattenMaterialSettings, SG_MATERIALBAKING_INDEX, MaterialInfoResult, CustomTextureCoordinates);

		// Copy custom texture coordinates to mesh settings
		for (const FString& MeshSectionId : MaterialInfoResult.ComponentMeshMaterialKeys)
		{
			FMeshData* MeshSettings = OutMeshMaterialInfo.BakeMaterialMeshSettings.Find(MeshSectionId);
			if (MeshSettings)
			{
				MeshSettings->CustomTextureCoordinates = CustomTextureCoordinates;
			}
		}
	}

	return Geometry;
}

void FSimplygonDataConverter::GetMaterialMaps(
	Simplygon::ISimplygon* /*sdk*/,
	const FMeshDescription& Mesh,
	TMap<FName, int32>& OutMaterialNameToIndex,
	TMap<int32, FName>& OutMaterialIndexToName,
	TArray<int32> MaterialIdRemap)
{
	TPolygonGroupAttributesConstRef<FName> PolygonGroupMaterialSlotName = Mesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);

	bool bRemapMaterialIds = MaterialIdRemap.Num() > 0;

	for (const FPolygonGroupID& PolygonGroupID : Mesh.PolygonGroups().GetElementIDs())
	{
		int32 MaterialId = bRemapMaterialIds ? MaterialIdRemap[PolygonGroupID.GetValue()] : PolygonGroupID.GetValue();

		FName MaterialName = PolygonGroupMaterialSlotName[PolygonGroupID];
		// If available, use MaterialId to fetch the material name
		if (MaterialId < PolygonGroupMaterialSlotName.GetNumElements())
			MaterialName = PolygonGroupMaterialSlotName[FPolygonGroupID(MaterialId)];

		OutMaterialNameToIndex.Add(MaterialName, MaterialId);
		OutMaterialIndexToName.Add(MaterialId, MaterialName);
	}
}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryFromMeshDescription(
	Simplygon::ISimplygon* SDK,
	const FMeshDescription& Mesh,
	const TMap<FName, int32>& MaterialMap,
	const TArray<FBox2D>& TextureBounds,
	const TArray<FVector2D>& InTexCoords,
	bool SetVertexWeights)
{
	int32 NumTris = 0;
	for (const FPolygonID& PolygonID : Mesh.Polygons().GetElementIDs())
		NumTris += Mesh.GetPolygonTriangleIDs(PolygonID).Num();

	int32 NumWedges = NumTris * 3;
	int32 NumVertices = Mesh.Vertices().Num();

	auto VertexPositions =
		Mesh.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
	auto VertexInstanceNormals =
		Mesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
	auto VertexInstanceTangents =
		Mesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
	auto VertexInstanceBinormalSigns = Mesh.VertexInstanceAttributes().GetAttributesRef<float>(
		MeshAttribute::VertexInstance::BinormalSign);

	auto VertexInstanceColors =
		Mesh.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);

	auto PolygonGroupMaterialSlotName = Mesh.PolygonGroupAttributes().GetAttributesRef<FName>(
		MeshAttribute::PolygonGroup::ImportedMaterialSlotName);

	auto VertexInstanceUVs = Mesh.VertexInstanceAttributes().GetAttributesRef<FVector2D>(
		MeshAttribute::VertexInstance::TextureCoordinate);
	int32 NumTextureCoordinates = VertexInstanceUVs.GetNumIndices();

	TArray<FLinearColor> VertexColors;
	TArray<int32> UniqueIndexCount;
	if (SetVertexWeights)
	{
		VertexColors.AddZeroed(NumVertices);
		UniqueIndexCount.AddZeroed(NumVertices);
	}

	Simplygon::spGeometryData OutGeometryData = SDK->CreateGeometryData();
	OutGeometryData->SetVertexCount(NumVertices);
	OutGeometryData->SetTriangleCount(NumTris);

	Simplygon::spRealArray Positions = OutGeometryData->GetCoords();
	Simplygon::spRidArray Indices = OutGeometryData->GetVertexIds();

	OutGeometryData->AddNormals();
	Simplygon::spRealArray Normals = OutGeometryData->GetNormals();

	OutGeometryData->AddTangents(0);
	Simplygon::spRealArray Tangents = OutGeometryData->GetTangents(0);
	Simplygon::spRealArray Bitangents = OutGeometryData->GetBitangents(0);

	OutGeometryData->AddColors(0);
	Simplygon::spRealArray LinearColors = OutGeometryData->GetColors(0);

	OutGeometryData->AddMaterialIds();
	Simplygon::spRidArray MaterialIndices = OutGeometryData->GetMaterialIds();

	OutGeometryData->AddGroupIds();
	Simplygon::spRidArray GroupIds = OutGeometryData->GetGroupIds();

	// Positions
	TArray<int32> RemapVerts;
	RemapVerts.AddZeroed(Mesh.Vertices().GetArraySize());
	int32 VertexIndex = 0, ComponentIndex = 0;
	Simplygon::real* TransformedPositions = new Simplygon::real[Mesh.Vertices().Num() * 3];
	for (const FVertexID& VertexID : Mesh.Vertices().GetElementIDs())
	{
		FVector TempPos = VectorToSimplygon(VertexPositions[VertexID]);
		TransformedPositions[ComponentIndex++] = TempPos.X;
		TransformedPositions[ComponentIndex++] = TempPos.Y;
		TransformedPositions[ComponentIndex++] = TempPos.Z;

		RemapVerts[VertexID.GetValue()] = VertexIndex++;
	}
	Positions->SetData(TransformedPositions, VertexIndex * 3);
	delete[] TransformedPositions;

	auto AddTexCoords = [&OutGeometryData, &Mesh, &MaterialMap, &PolygonGroupMaterialSlotName, &NumWedges, &VertexInstanceUVs](
							int32 TexCoordIndex,
							const TArray<FBox2D>& TextureBounds,
							const TArray<FVector2D>& CustomUVs) {
		bool bUseCustomUVs = (TexCoordIndex == SG_MATERIALBAKING_INDEX && CustomUVs.Num() == NumWedges);

		OutGeometryData->AddTexCoords(TexCoordIndex);
		Simplygon::spRealArray TexCoords = OutGeometryData->GetTexCoords(TexCoordIndex);
		check(TexCoords->GetTupleSize() == 2);

		int32 TriangleIndex = 0;
		Simplygon::real* UVData = new Simplygon::real[TexCoords->GetTupleCount() * 2];
		Simplygon::real* UVComponent = UVData;
		for (const FPolygonID PolygonID : Mesh.Polygons().GetElementIDs())
		{
			const FPolygonGroupID& PolygonGroupID = Mesh.GetPolygonPolygonGroup(PolygonID);

			//Note : checking if material map contains any items. For culling/occluding geometries a material mapping might not be required. Default it to 0 (Assumption : -1 might not work with Simplygon API)
			const FName SlotName = PolygonGroupMaterialSlotName[PolygonGroupID];
			const int32* ValuePtr = MaterialMap.Find(SlotName);
			const int32 MaterialIndex = ValuePtr == nullptr ? 0 : *ValuePtr;

			if (!ValuePtr)
			{
				UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Could not find material slot name %s, therefore defaulting to index 0"), *(SlotName.ToString()));
			}

			const TArray<FTriangleID>& TriangleIDs = Mesh.GetPolygonTriangleIDs(PolygonID);
			for (const FTriangleID& MeshTriangleID : TriangleIDs)
			{
				// Adjust UVs when baking materials
				float MinU = 0, ScaleU = 1;
				float MinV = 0, ScaleV = 1;
				if (TextureBounds.IsValidIndex(MaterialIndex) && TexCoordIndex == 0 &&
					CustomUVs.Num() == 0)
				{
					const FBox2D& Bounds = TextureBounds[MaterialIndex];
					if (Bounds.GetArea() > 0)
					{
						MinU = float(Bounds.Min.X);
						MinV = float(Bounds.Min.Y);
						ScaleU = 1.0f / float(Bounds.Max.X - Bounds.Min.X);
						ScaleV = 1.0f / float(Bounds.Max.Y - Bounds.Min.Y);
					}
				}

				for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
				{
					const FVertexInstanceID VertexInstanceID = Mesh.GetTriangleVertexInstance(MeshTriangleID, CornerIndex);

					const FVector2D& TexCoord =
						bUseCustomUVs ? CustomUVs[TriangleIndex * 3 + CornerIndex] : VertexInstanceUVs.Get(VertexInstanceID, TexCoordIndex);
					*UVComponent++ = (TexCoord.X - MinU) * ScaleU;
					*UVComponent++ = (TexCoord.Y - MinV) * ScaleV;
				}
				++TriangleIndex;
			}
		}
		TexCoords->SetData(UVData, TexCoords->GetTupleCount() * 2);
		delete[] UVData;
	};

	// Texture coordinates (Material baking)
	if (InTexCoords.Num() == NumWedges)
		AddTexCoords(SG_MATERIALBAKING_INDEX, TextureBounds, InTexCoords);

	// Texture coordinates (Original)
	for (int32 TexCoordIndex = 0; TexCoordIndex < NumTextureCoordinates; ++TexCoordIndex)
	{
		AddTexCoords(TexCoordIndex, TextureBounds, InTexCoords);
	}

	Simplygon::rid* MaterialIndexData = new Simplygon::rid[MaterialIndices->GetItemCount()];
	Simplygon::rid* MaterialIndexComponent = MaterialIndexData;
	Simplygon::rid* GroupIdData = new Simplygon::rid[GroupIds->GetItemCount()];
	Simplygon::rid* GroupIdComponent = GroupIdData;
	Simplygon::rid* IndexData = new Simplygon::rid[Indices->GetItemCount()];
	Simplygon::rid* IndexComponent = IndexData;
	Simplygon::real* NormalData = new Simplygon::real[Normals->GetTupleCount() * 3];
	Simplygon::real* NormalComponent = NormalData;
	Simplygon::real* TangentData = new Simplygon::real[Tangents->GetTupleCount() * 3];
	Simplygon::real* TangentComponent = TangentData;
	Simplygon::real* BitangentData = new Simplygon::real[Bitangents->GetTupleCount() * 3];
	Simplygon::real* BitangentComponent = BitangentData;
	Simplygon::real* ColorData = new Simplygon::real[LinearColors->GetTupleCount() * 4];
	Simplygon::real* ColorComponent = ColorData;
	for (const FPolygonID PolygonID : Mesh.Polygons().GetElementIDs())
	{
		const FPolygonGroupID& PolygonGroupID = Mesh.GetPolygonPolygonGroup(PolygonID);

		const TArray<FTriangleID>& TriangleIDs = Mesh.GetPolygonTriangleIDs(PolygonID);
		for (const FTriangleID& MeshTriangleID : TriangleIDs)
		{
			// Material Indices
			//Note : checking if material map contains any items. For culling/occluding geometries a material mapping might not be required. Default it to 0 (Assumption : -1 might not work with Simplygon API)
			const FName SlotName = PolygonGroupMaterialSlotName[PolygonGroupID];
			const int32* ValuePtr = MaterialMap.Find(SlotName);
			const int32 MaterialIndex = ValuePtr == nullptr ? 0 : *ValuePtr;

			if (!ValuePtr)
			{
				UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Could not find material slot name %s, therefore defaulting to index 0"), *(SlotName.ToString()));
			}

			*MaterialIndexComponent++ = MaterialIndex;

			// Smoothing groups
			*GroupIdComponent++ = 0;

			for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
			{
				const FVertexInstanceID VertexInstanceID = Mesh.GetTriangleVertexInstance(MeshTriangleID, CornerIndex);

				// Vertex Indices
				*IndexComponent++ = RemapVerts[Mesh.GetVertexInstanceVertex(VertexInstanceID).GetValue()];

				// Normals
				FVector Normal = VectorToSimplygon(VertexInstanceNormals[VertexInstanceID]);
				*NormalComponent++ = Normal.X;
				*NormalComponent++ = Normal.Y;
				*NormalComponent++ = Normal.Z;

				// Tangents
				FVector Tangent = VectorToSimplygon(VertexInstanceTangents[VertexInstanceID]);
				*TangentComponent++ = Tangent.X;
				*TangentComponent++ = Tangent.Y;
				*TangentComponent++ = Tangent.Z;

				// Bitangents
				FVector Bitangent = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID],
										VertexInstanceTangents[VertexInstanceID])
										.GetSafeNormal() *
									VertexInstanceBinormalSigns[VertexInstanceID];
				Bitangent = VectorToSimplygon(Bitangent);
				*BitangentComponent++ = Bitangent.X;
				*BitangentComponent++ = Bitangent.Y;
				*BitangentComponent++ = Bitangent.Z;

				// Vertex Colors
				FLinearColor VertexColor =
					FLinearColor(VertexInstanceColors[VertexInstanceID]).ToFColor(true);
				*ColorComponent++ = VertexColor.R;
				*ColorComponent++ = VertexColor.G;
				*ColorComponent++ = VertexColor.B;
				*ColorComponent++ = VertexColor.A;

				// Vertex Weights (based on vertex colors)
				if (SetVertexWeights)
				{
					const int32 VertexInd =
						RemapVerts[Mesh.GetVertexInstanceVertex(VertexInstanceID).GetValue()];
					VertexColors[VertexInd] += VertexColor;
					UniqueIndexCount[VertexInd] += 1;
				}
			}
		}
	}
	MaterialIndices->SetData(MaterialIndexData, MaterialIndices->GetItemCount());
	GroupIds->SetData(GroupIdData, GroupIds->GetItemCount());
	Indices->SetData(IndexData, Indices->GetItemCount());
	Normals->SetData(NormalData, Normals->GetItemCount());
	Tangents->SetData(TangentData, Tangents->GetItemCount());
	Bitangents->SetData(BitangentData, Bitangents->GetItemCount());
	LinearColors->SetData(ColorData, LinearColors->GetItemCount());
	delete[] MaterialIndexData;
	delete[] GroupIdData;
	delete[] IndexData;
	delete[] NormalData;
	delete[] TangentData;
	delete[] BitangentData;
	delete[] ColorData;

	if (SetVertexWeights)
	{
		if (OutGeometryData->GetVertexWeights().IsNull())
		{
			OutGeometryData->AddVertexWeights();
		}
		Simplygon::spRealArray VertexWeights = OutGeometryData->GetVertexWeights();

		// Assign the average wedge color as vertex weights
		float VertexWeight = 1.0f;
		float Alpha = 0.0f;
		for (int32 VertexIdx = 0; VertexIdx < NumVertices; ++VertexIdx)
		{
			/*
			 * Alpha = 0,		 Reduce
			 * Alpha = 0.5,		 Neutral
			 * Alpha = 1.0,		 Keep
			 */
			VertexWeight = 1.0f;
			FLinearColor& Color = VertexColors[VertexIdx];
			Alpha = Color.A / UniqueIndexCount[VertexIdx];
			VertexWeight = ConvertFromColorToWeights(Alpha, 8.0f);
			VertexWeights->SetItem(VertexIdx, VertexWeight);
		}
	}

	return OutGeometryData;
}

void FSimplygonDataConverter::CreateMeshDescriptionFromGeometry(
	const Simplygon::spGeometryData& GeometryData,
	const TMap<int32, FName>& MaterialIndexToSlotName,
	FMeshDescription& OutMesh,
	TMap<FPolygonGroupID, int32>& OutPolyGroupToMaterialIndex,
	TArray<uint32>& OutBakedUVSets,
	bool bHasBakedMaterials,
	bool bKeepSoureUVsIntact)
{
	check(GeometryData);

	Simplygon::spRealArray Positions = GeometryData->GetCoords();
	Simplygon::spRidArray Indices = GeometryData->GetVertexIds();
	Simplygon::spRidArray MaterialIndices = GeometryData->GetMaterialIds();
	Simplygon::spRidArray GroupIds = GeometryData->GetGroupIds();
	Simplygon::spRealArray LinearColors = GeometryData->GetColors(0);
	Simplygon::spRealArray Normals = GeometryData->GetNormals();
	Simplygon::spRealArray Tangents = bHasBakedMaterials ? GeometryData->GetTangents(SG_MATERIALBAKING_INDEX) : GeometryData->GetTangents(0);
	Simplygon::spRealArray Bitangents = bHasBakedMaterials ? GeometryData->GetBitangents(SG_MATERIALBAKING_INDEX) : GeometryData->GetBitangents(0);
	Simplygon::spRealArray CustomTexCoordsFromFlatten = GeometryData->GetTexCoords(SG_MATERIALBAKING_INDEX);

	bool bHasTangnets = Tangents && !Tangents->IsEmpty();
	bool bHasBitangents = Bitangents && !Bitangents->IsEmpty();
	bool bHasLinearColors = LinearColors && !LinearColors->IsEmpty();

	bool bHasCustomUVs = bHasBakedMaterials && CustomTexCoordsFromFlatten != nullptr;
	TArray<uint32> UVSetsForBakedTextures;
	TMap<uint32, Simplygon::spRealArray> TexCoordArrays;

	//NOTE: This will fetch the UVset from Simplygon.Note the index passed in here is UE based 0 - 7
	// If there is a baked material it will be inserted into the first free index else if will raise an error.
	// End users can choose to remvoe uv sets on LODs from the LODRecipe settings.
	auto GetTexCoords = [&bHasCustomUVs, &CustomTexCoordsFromFlatten, &GeometryData, &UVSetsForBakedTextures, &bKeepSoureUVsIntact](int32 TexCoordIndex) {
		if (!bKeepSoureUVsIntact && bHasCustomUVs && TexCoordIndex == 0)
			return CustomTexCoordsFromFlatten;

		auto TexCoords = GeometryData->GetTexCoords(TexCoordIndex);
		if (TexCoords.IsNull() && bHasCustomUVs && UVSetsForBakedTextures.Num() == 0)
		{
			UVSetsForBakedTextures.Add(TexCoordIndex);
			return CustomTexCoordsFromFlatten;
		}
		return TexCoords;
	};

	check(Positions);
	check(Indices);

	int32 NumTriangles = GeometryData->GetTriangleCount();
	int32 NumCorners = NumTriangles * 3;
	int32 NumVertices = GeometryData->GetVertexCount();

	OutMesh.Empty();
	OutMesh.ReserveNewVertexInstances(NumCorners);
	OutMesh.ReserveNewPolygons(NumTriangles);
	// Approximately 2.5 edges per polygons
	OutMesh.ReserveNewEdges(int32(float(NumCorners) * 2.5f / 3.0f));
	OutMesh.ReserveNewVertices(NumVertices);

	// Gather all array data
	TVertexAttributesRef<FVector> VertexPositions =
		OutMesh.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);

	TVertexInstanceAttributesRef<FVector> VertexInstanceNormals =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
	TVertexInstanceAttributesRef<FVector> VertexInstanceTangents =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(
			MeshAttribute::VertexInstance::Tangent);
	TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<float>(
			MeshAttribute::VertexInstance::BinormalSign);
	TVertexInstanceAttributesRef<FVector4> VertexInstanceColors =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
	TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<FVector2D>(
			MeshAttribute::VertexInstance::TextureCoordinate);

	TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames =
		OutMesh.PolygonGroupAttributes().GetAttributesRef<FName>(
			MeshAttribute::PolygonGroup::ImportedMaterialSlotName);

	// Pre-setup texture coordinates
	int32 NumTexCoords = 0;
	int32 MaxTexCoords = MAX_MESH_TEXTURE_COORDS_MD;
	TArray<int32> TextureCoordinateRemapIndex;
	TextureCoordinateRemapIndex.AddZeroed(MaxTexCoords);
	for (int32 TextureCoordinateIndex = 0; TextureCoordinateIndex < MaxTexCoords; ++TextureCoordinateIndex)
	{
		TextureCoordinateRemapIndex[TextureCoordinateIndex] = INDEX_NONE;

		Simplygon::spRealArray TexCoords = GetTexCoords(TextureCoordinateIndex);
		if (TexCoords)
		{
			TextureCoordinateRemapIndex[TextureCoordinateIndex] = NumTexCoords;
			TexCoordArrays.Add(NumTexCoords, TexCoords);
			NumTexCoords++;
		}
	}

	if (bHasBakedMaterials && UVSetsForBakedTextures.Num() == 0)
	{
		UE_LOG(LogSimplygonDataConverter, Error, TEXT("Could not find a free UV slot for UV set used by the the baked material."));
	}

	verify(NumTexCoords > 0);
	VertexInstanceUVs.SetNumIndices(NumTexCoords);

	// Positions
	TArray<FVertexID> VertexIndexToVertexID;
	VertexIndexToVertexID.AddUninitialized(NumVertices);
	for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
	{
		Simplygon::spRealData tuple = Positions->GetTuple(VertexIndex);
		const Simplygon::real* vertexPos = tuple.Data();

		FVertexID VertexID = OutMesh.CreateVertex();
		VertexPositions[VertexID] = VectorFromSimplygon(
			FVector(vertexPos[0], vertexPos[1], vertexPos[2]));
		VertexIndexToVertexID[VertexIndex] = VertexID;
	}

	// Polygon Groups
	TArray<FPolygonGroupID> PolygonGroups;
	TMap<int32, FPolygonGroupID> MaterialIndexToPolygonGroup;
	for (int32 TriIndex = 0; TriIndex < NumTriangles; ++TriIndex)
	{
		const int32 MaterialIndex = MaterialIndices->GetItem(TriIndex);

		if (!MaterialIndexToPolygonGroup.Contains(MaterialIndex))
		{
			FPolygonGroupID PolygonGroupID = OutMesh.CreatePolygonGroup();
			PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString::Printf(TEXT("MaterialSlot_%d"), MaterialIndex));

			// A material slot name should always exist for meshes w/o material baking
			if (MaterialIndexToSlotName.Contains(MaterialIndex))
			{
				PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = MaterialIndexToSlotName[MaterialIndex];
			}

			PolygonGroups.Add(PolygonGroupID);
			MaterialIndexToPolygonGroup.Add(MaterialIndex, PolygonGroupID);
			OutPolyGroupToMaterialIndex.Add(PolygonGroupID, MaterialIndex);
		}
	}

	// Triangles
	for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
	{
		int32 VerticeIndexBase = TriangleIndex * 3;

		// PolygonGroup
		FPolygonGroupID PolygonGroupID = FPolygonGroupID::Invalid;

		int32 MaterialIndex = MaterialIndices->GetItem(TriangleIndex);
		if (MaterialIndexToPolygonGroup.Contains(MaterialIndex))
		{
			PolygonGroupID = MaterialIndexToPolygonGroup[MaterialIndex];
		}

		if (PolygonGroupID == FPolygonGroupID::Invalid)
		{
			PolygonGroupID = OutMesh.CreatePolygonGroup();
			PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString::Printf(TEXT("MaterialSlot_%d"), MaterialIndex));
			PolygonGroups.Add(PolygonGroupID);
			MaterialIndexToPolygonGroup.Add(MaterialIndex, PolygonGroupID);
		}

		TArray<FVertexInstanceID> TriangleVertexInstanceIDs;
		TriangleVertexInstanceIDs.SetNum(3);
		for (int32 Corner = 0; Corner < 3; ++Corner)
		{
			int32 VerticeIndex = VerticeIndexBase + Corner;
			FVertexID VertexID = VertexIndexToVertexID[Indices->GetItem(VerticeIndex)];

			FVertexInstanceID VertexInstanceID = OutMesh.CreateVertexInstance(VertexID);
			TriangleVertexInstanceIDs[Corner] = VertexInstanceID;

			// Vertex colors
			if (bHasLinearColors)
			{
				Simplygon::spRealData tuple = LinearColors->GetTuple(VerticeIndex);
				const Simplygon::real* sgVertexColor = tuple.Data();
				FLinearColor LinearColor(sgVertexColor[0], sgVertexColor[1], sgVertexColor[2],
					sgVertexColor[3]);
				VertexInstanceColors[VertexInstanceID] =
					FLinearColor::FromSRGBColor(LinearColor.ToFColor(true));
			}
			else
			{
				VertexInstanceColors[VertexInstanceID] = FLinearColor::White;
			}

			// Normal
			Simplygon::spRealData tuple = Normals->GetTuple(VerticeIndex);
			const Simplygon::real* sgNormal = tuple.Data();
			FVector Normal = VectorFromSimplygon(FVector(sgNormal[0], sgNormal[1], sgNormal[2]));
			VertexInstanceNormals[VertexInstanceID] = Normal;

			// Tangent
			if (bHasTangnets)
			{
				tuple = Tangents->GetTuple(VerticeIndex);
				const Simplygon::real* sgTangents = tuple.Data();
				FVector Tangent = VectorFromSimplygon(FVector(sgTangents[0], sgTangents[1], sgTangents[2]));
				VertexInstanceTangents[VertexInstanceID] = Tangent;

				// Bitangent
				if (bHasBitangents)
				{
					tuple = Bitangents->GetTuple(VerticeIndex);
					const Simplygon::real* sgBitangents = tuple.Data();
					FVector Bitangent = VectorFromSimplygon(FVector(sgBitangents[0], sgBitangents[1], sgBitangents[2]));
					VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign(
						Tangent.GetSafeNormal(), Bitangent.GetSafeNormal(), Normal.GetSafeNormal());
				}
			}

			// Texture Coordinates
			for (int32 TextureCoordinateIndex = 0; TextureCoordinateIndex < NumTexCoords;
				 ++TextureCoordinateIndex)
			{
				int32 TextureCoordIndex = TextureCoordinateRemapIndex[TextureCoordinateIndex];
				if (TextureCoordIndex == INDEX_NONE)
				{
					continue;
				}

				Simplygon::spRealArray TexCoords = TexCoordArrays[TextureCoordIndex];

				if (UVSetsForBakedTextures.Contains(TextureCoordinateIndex))
					OutBakedUVSets.Add(TextureCoordIndex);

				tuple = TexCoords->GetTuple(VerticeIndex);
				const Simplygon::real* sgTextCoords = tuple.Data();

				VertexInstanceUVs.Set(VertexInstanceID, TextureCoordIndex,
					FVector2D(sgTextCoords[0], sgTextCoords[1]));
			}
		}

		OutMesh.CreatePolygon(PolygonGroupID, TriangleVertexInstanceIDs);
		/*const FPolygonID NewPolygonID = OutMesh.CreatePolygon(PolygonGroupID, TriangleVertexInstanceIDs);
		int32 NewTriangleIndex = OutMesh.GetPolygonTriangles(NewPolygonID).AddDefaulted();
		FMeshTriangle& NewTriangle = OutMesh.GetPolygonTriangles(NewPolygonID)[NewTriangleIndex];
		for (int32 Corner = 0; Corner < 3; ++Corner)
		{
			FVertexInstanceID VertexInstanceID = TriangleVertexInstanceIDs[Corner];
			NewTriangle.SetVertexInstanceID(Corner, VertexInstanceID);
		}*/
	}
}

bool FSimplygonDataConverter::AddSimplygonMaterials(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, const TArray<FFlattenMaterial>& InputMaterials, bool bReleaseInputMaterials)
{
	if (InputMaterials.Num() == 0)
	{
		UE_LOG(LogSimplygonDataConverter, Log, TEXT("Input meshes do not contain any materials. A proxy without material will be generated."));
		return false;
	}

	FCriticalSection TableMutex;
	Simplygon::spTextureTable TextureTable = Scene->GetTextureTable();
	Simplygon::spMaterialTable MaterialTable = Scene->GetMaterialTable();

	TArray<Simplygon::spMaterial> MaterialArray;
	MaterialArray.SetNum(InputMaterials.Num());
	ParallelFor(InputMaterials.Num(), [&](int32 MaterialIndex) {
		const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex];
		Simplygon::spMaterial SgMaterial = SDK->CreateMaterial();
		SgMaterial->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("FlattenMaterial%d"), MaterialIndex)));

		// Make sure we don't have any pre-added channels
		RemoveAllMaterialChannels(SgMaterial);

		for (const auto& ChannelToProperty : FSimplygonMaterialChannelConverter::GetChannelToPropertyMap())
		{
			EFlattenMaterialProperties MaterialProperty = ChannelToProperty.Value;

			bool sRGB = true;
			if (MaterialProperty == EFlattenMaterialProperties::Normal || MaterialProperty == EFlattenMaterialProperties::Opacity || MaterialProperty == EFlattenMaterialProperties::OpacityMask)
				sRGB = false;

			bool bHasPropertyData = FlattenMaterial.DoesPropertyContainData(MaterialProperty);
			if (bHasPropertyData)
			{
				SetMaterialChannelData(
					SDK,
					FlattenMaterial.GetPropertySamples(MaterialProperty),
					FlattenMaterial.GetPropertySize(MaterialProperty),
					SgMaterial,
					TextureTable,
					TableMutex,
					TCHAR_TO_ANSI(*ChannelToProperty.Key),
					sRGB,
					FlattenMaterial.EmissiveScale);
			}
			//SG-TODO: Do we need this special treatment?
			else if (!bHasPropertyData && (MaterialProperty == EFlattenMaterialProperties::Emissive))
			{
				TArray<FColor> BlackEmissive;
				BlackEmissive.AddZeroed(1);
				SetMaterialChannelData(SDK, BlackEmissive, FIntPoint(1, 1), SgMaterial, TextureTable, TableMutex, TCHAR_TO_ANSI(*ChannelToProperty.Key), sRGB, FlattenMaterial.EmissiveScale);
			}
			
			if (bHasPropertyData && MaterialProperty == EFlattenMaterialProperties::OpacityMask)
			{
				SgMaterial->SetBlendMode(Simplygon::EMaterialBlendMode::Mask);
				SgMaterial->SetOpacityCutoff(0.333f);
				SgMaterial->SetOpacityType(Simplygon::EOpacityType::Opacity);
			}
			if (bHasPropertyData && MaterialProperty == EFlattenMaterialProperties::Opacity)
			{
				SgMaterial->SetBlendMode(Simplygon::EMaterialBlendMode::Blend);
				SgMaterial->SetOpacityType(Simplygon::EOpacityType::Opacity);
			}
			
		}

		MaterialArray[MaterialIndex] = SgMaterial;
	});

	for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++)
	{
		const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex];

		MaterialTable->AddMaterial(MaterialArray[MaterialIndex]);

		if (bReleaseInputMaterials)
		{
			// Release FlattenMaterial. Using const_cast here to avoid removal of "const" from input data here
			// and above the call chain.
			const_cast<FFlattenMaterial*>(&FlattenMaterial)->ReleaseData();
		}
	}

	return true;
}

void FSimplygonDataConverter::AddDefaultDiffuseToMaterial(FFlattenMaterial& OutMaterial)
{
	const FIntPoint ConstantSize(1, 1);
	OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, ConstantSize);

	TArray<FColor>& BaseColorSamples = OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse);
	BaseColorSamples.Empty();
	BaseColorSamples.Add(FColor::Black);
}

bool FSimplygonDataConverter::CreateFlattenMaterial(Simplygon::spScene Scene, FFlattenMaterial& OutMaterial, int32 OutputMaterialIndex)
{
	bool bHasBakedMaterials = false;

	const FString SimplygonCastMaterial = "SimplygonCastMaterial";
	for (uint32 MaterialIter = 0; MaterialIter < Scene->GetMaterialTable()->GetMaterialsCount(); ++MaterialIter)
	{
		auto Material = Scene->GetMaterialTable()->GetMaterial(MaterialIter);
		FString MaterialName = ANSI_TO_TCHAR(Material->GetName().c_str());
		if (MaterialName.Equals(SimplygonCastMaterial))
			OutputMaterialIndex = MaterialIter;
	}

	Simplygon::spMaterial Material = Scene->GetMaterialTable()->GetMaterial(OutputMaterialIndex);
	if (Material.IsNull())
		return false;

	Simplygon::spTextureTable TextureTable = Scene->GetTextureTable();

	int32 NumMaterialChannels = Material->GetMaterialChannelCount();
	if (NumMaterialChannels <= 0)
		return false;

	bool bHasBaseColor = false;

	for (int32 ChannelIndex = 0; ChannelIndex < NumMaterialChannels; ++ChannelIndex)
	{
		Simplygon::spString MaterialChannelName = Material->GetMaterialChannelFromIndex(ChannelIndex);
		FString ChannelName = ANSI_TO_TCHAR(MaterialChannelName.c_str());

		EFlattenMaterialProperties MaterialProperty;
		if (!FSimplygonMaterialChannelConverter::MaterialChannelToProperty(ChannelName, MaterialProperty))
			continue;

		FIntPoint Size = FIntPoint::ZeroValue;
		GetMaterialChannelData(Material, TextureTable, MaterialChannelName, OutMaterial.GetPropertySamples(MaterialProperty), Size, OutMaterial.EmissiveScale);
		if (Size == FIntPoint::ZeroValue)
			continue;

		OutMaterial.SetPropertySize(MaterialProperty, Size);

		if (MaterialProperty == EFlattenMaterialProperties::Diffuse)
			bHasBaseColor = true;

		bHasBakedMaterials = true;
	}

	// Setup constant value if there is no texture data.
	// NOTE: When we convert to Unreal materials we rely on ProxyMaterialUtilities::CreateProxyMaterialInstance,
	//       which expects BaseColor to have either texture data or constant value.
	if (!bHasBaseColor)
	{
		FIntPoint ConstantSize(1, 1);
		OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, ConstantSize);

		TArray<FColor>& BaseColorSamples = OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse);
		BaseColorSamples.Empty();
		BaseColorSamples.Add(FColor::Black);
	}

	return bHasBakedMaterials;
}

bool FSimplygonDataConverter::CreateGeometryDataTextures(Simplygon::spScene Scene, Simplygon::spPipeline Pipeline, const FString& BaseTextureName, TArray<UObject*>& AssetsToSync)
{
	bool bHasDataCasters = false;

	if (Scene->GetMaterialTable().GetMaterialsCount() <= 0)
		return false;

	Simplygon::spMaterial Material = Scene->GetMaterialTable()->GetMaterial(0);
	if (Material.IsNull())
		return false;

	Simplygon::spTextureTable TextureTable = Scene->GetTextureTable();

	int32 NumMaterialChannels = Material->GetMaterialChannelCount();
	if (NumMaterialChannels <= 0)
		return false;

	// Get casted geometry data channels from pipeline and use it for lookup below.
	const TArray<FString> GeometryCasterChannels = [&Pipeline]() {
		TArray<FString> MaterialChannels;

		Simplygon::spObjectCollection MaterialCasters = Pipeline.GetMaterialCasters();
		for (Simplygon::rhandle CasterHandle = MaterialCasters->GetFirstItem();
			 CasterHandle;
			 CasterHandle = MaterialCasters->GetNextItem(CasterHandle))
		{
			auto GeometryCaster = Simplygon::spGeometryDataCaster::SafeCast(MaterialCasters.GetItemsObject(CasterHandle));
			if (GeometryCaster.IsNull())
				continue;

			auto GeometryDataCasterSettings = GeometryCaster.GetGeometryDataCasterSettings();
			FString ChannelName = ANSI_TO_TCHAR(GeometryDataCasterSettings.GetMaterialChannel().c_str());
			MaterialChannels.Add((ChannelName));
		}
		return MaterialChannels;
	}();

	if (GeometryCasterChannels.Num() <= 0)
		return false;

	for (const FString& GeometryCasterChannel : GeometryCasterChannels)
	{
		FIntPoint Size = FIntPoint::ZeroValue;

		TArray<FColor> Samples8;
		TArray<FFloat16Color> Samples16;
		float DummyEmissiveScale;
		GetMaterialChannelData(Material, TextureTable, TCHAR_TO_ANSI(*GeometryCasterChannel), Samples8, Size, DummyEmissiveScale, &Samples16);

		const bool bHas16bitSamples = Samples16.Num() > 0;

		const FString SimplygonGeomDataTextureDir = FPaths::Combine(TEXT("/Game"), TEXT("Simplygon"), TEXT("LODRecipe"), TEXT("Materials"));
		const FString AbsoluteTextureName = FPaths::Combine(SimplygonGeomDataTextureDir, TEXT("T_") + BaseTextureName + TEXT("_") + GeometryCasterChannel);
		const FString TextureName = FPackageName::GetShortName(AbsoluteTextureName);

		UPackage* TexturePackage = CreatePackage(*AbsoluteTextureName);
		TexturePackage->FullyLoad();
		TexturePackage->Modify();

		ETextureSourceFormat TexSrcFormat = bHas16bitSamples ? TSF_RGBA16F : TSF_BGRA8;
		UTexture2D* GeometryDataTexture = NewObject<UTexture2D>(TexturePackage, *TextureName, RF_Public | RF_Standalone);
		GeometryDataTexture->Source.Init(Size.X, Size.Y, /*NumSlices=*/1, /*NumMips=*/1, TexSrcFormat);
		GeometryDataTexture->MipGenSettings = TMGS_NoMipmaps;
		GeometryDataTexture->SRGB = false;
		GeometryDataTexture->CompressionNone = true;
		GeometryDataTexture->LODGroup = bHas16bitSamples ? TEXTUREGROUP_16BitData : TEXTUREGROUP_8BitData;

		// Copy data to texture
		const int32 TextureDataSize = int32(GeometryDataTexture->Source.CalcMipSize(0));
		if (bHas16bitSamples)
		{
			check(TextureDataSize == Samples16.Num() * sizeof(FFloat16Color));
			FFloat16Color* TextureData = (FFloat16Color*)GeometryDataTexture->Source.LockMip(0);
			FMemory::Memcpy(TextureData, Samples16.GetData(), TextureDataSize);
		}
		else
		{
			check(TextureDataSize == Samples8.Num() * sizeof(FColor));
			FColor* TextureData = (FColor*)GeometryDataTexture->Source.LockMip(0);
			FMemory::Memcpy(TextureData, Samples8.GetData(), Samples8.Num() * sizeof(FColor));
		}

		GeometryDataTexture->Source.UnlockMip(0);
		GeometryDataTexture->PostEditChange();

		AssetsToSync.Add(GeometryDataTexture);

		bHasDataCasters = true;
	}

	return bHasDataCasters;
}

void FSimplygonDataConverter::CreateMaterialCasters(Simplygon::ISimplygon* SDK, Simplygon::spPipeline Pipeline, const TArray<FFlattenMaterial>& InputMaterials)
{
	bool bHasDiffuseCaster = false;
	bool bHasMetallicCaster = false;
	bool bHasSpecularCaster = false;
	bool bHasRoughnessCaster = false;
	bool bHasAOCaster = false;
	bool bHasNormalCaster = false;
	bool bHasOpacityCaster = false;
	bool bHasOpacityMaskCaster = false;
	bool bHasEmissiveCaster = false;
	bool bHasSubsurfaceCaster = false;
	for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++)
	{
		auto& InputMaterial = InputMaterials[MaterialIndex];
		if (!bHasDiffuseCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Diffuse))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_BASECOLOR;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::Diffuse, ChannelName);
			Simplygon::spColorCaster diffuseCaster = SDK->CreateColorCaster();
			diffuseCaster->GetColorCasterSettings()->SetOutputSRGB(true);
			diffuseCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8A8);
			diffuseCaster->GetColorCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			Pipeline->AddMaterialCaster(diffuseCaster, 0);
			bHasDiffuseCaster = true;
		}
		if (!bHasMetallicCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Metallic))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_METALNESS;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::Metallic, ChannelName);
			Simplygon::spColorCaster metallicCaster = SDK->CreateColorCaster();
			metallicCaster->GetColorCasterSettings()->SetOutputSRGB(true);
			metallicCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8);
			metallicCaster->GetColorCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			Pipeline->AddMaterialCaster(metallicCaster, 0);
			bHasMetallicCaster = true;
		}
		if (!bHasSpecularCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Specular))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_SPECULAR;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::Metallic, ChannelName);
			Simplygon::spColorCaster specularCaster = SDK->CreateColorCaster();
			specularCaster->GetColorCasterSettings()->SetOutputSRGB(true);
			specularCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8A8);
			specularCaster->GetColorCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			Pipeline->AddMaterialCaster(specularCaster, 0);
			bHasSpecularCaster = true;
		}
		if (!bHasRoughnessCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Roughness))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_ROUGHNESS;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::Roughness, ChannelName);
			Simplygon::spColorCaster roughnessCaster = SDK->CreateColorCaster();
			roughnessCaster->GetColorCasterSettings()->SetOutputSRGB(true);
			roughnessCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8);
			roughnessCaster->GetColorCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			Pipeline->AddMaterialCaster(roughnessCaster, 0);
			bHasRoughnessCaster = true;
		}
		if (!bHasAOCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::AmbientOcclusion))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_OCCLUSION;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::AmbientOcclusion, ChannelName);
			Simplygon::spColorCaster aoCaster = SDK->CreateColorCaster();
			aoCaster->GetColorCasterSettings()->SetOutputSRGB(true);
			aoCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8);
			aoCaster->GetColorCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			Pipeline->AddMaterialCaster(aoCaster, 0);
			bHasAOCaster = true;
		}
		if (!bHasNormalCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Normal))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_NORMALS;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::Normal, ChannelName);
			Simplygon::spNormalCaster normalCaster = SDK->CreateNormalCaster();
			normalCaster->GetNormalCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8);
			normalCaster->GetNormalCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			normalCaster->GetNormalCasterSettings()->SetFlipBackfacingNormals(false);
			normalCaster->GetNormalCasterSettings()->SetGenerateTangentSpaceNormals(true);
			Pipeline->AddMaterialCaster(normalCaster, 0);
			bHasNormalCaster = true;
		}
		if (!bHasOpacityCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Opacity))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_OPACITY;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::Opacity, ChannelName);
			Simplygon::spColorCaster opacityCaster = SDK->CreateColorCaster();
			opacityCaster->GetColorCasterSettings()->SetOutputSRGB(false);
			opacityCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8A8);
			opacityCaster->GetColorCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			Pipeline->AddMaterialCaster(opacityCaster, 0);
			bHasOpacityCaster = true;
		}
		if (!bHasOpacityMaskCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::OpacityMask))
		{
			FString ChannelName = "OpacityMask";
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::OpacityMask, ChannelName);
			Simplygon::spColorCaster opacityCaster = SDK->CreateColorCaster();
			opacityCaster->GetColorCasterSettings()->SetOutputSRGB(false);
			opacityCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8);
			opacityCaster->GetColorCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			Pipeline->AddMaterialCaster(opacityCaster, 0);
			bHasOpacityMaskCaster = true;
		}
		if (!bHasEmissiveCaster && InputMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Num())
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_EMISSIVE;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::Emissive, ChannelName);
			Simplygon::spColorCaster emissiveCaster = SDK->CreateColorCaster();
			emissiveCaster->GetColorCasterSettings()->SetOutputSRGB(true);
			emissiveCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8A8);
			emissiveCaster->GetColorCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			Pipeline->AddMaterialCaster(emissiveCaster, 0);
			bHasEmissiveCaster = true;
		}
		if (!bHasSubsurfaceCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::SubSurface))
		{
			FString ChannelName = "SubSurface";
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel(EFlattenMaterialProperties::SubSurface, ChannelName);
			Simplygon::spColorCaster subsurfaceCaster = SDK->CreateColorCaster();
			subsurfaceCaster->GetColorCasterSettings()->SetOutputSRGB(true);
			subsurfaceCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8A8);
			subsurfaceCaster->GetColorCasterSettings()->SetMaterialChannel(TCHAR_TO_ANSI(*ChannelName));
			Pipeline->AddMaterialCaster(subsurfaceCaster, 0);
			bHasSubsurfaceCaster = true;
		}
	}
}

void FSimplygonDataConverter::CreateSkeletalHierarchy(Simplygon::ISimplygon* SDK, Simplygon::spScene& Scene, const FReferenceSkeleton& Skeleton, TArray<Simplygon::rid>& OutBoneTableIDs)
{
	TArray<Simplygon::spSceneBone> BoneArray;

	// Create Simplygon scene nodes for each bone in our Skeletal Hierarchy
	for (int32 BoneIndex = 0; BoneIndex < Skeleton.GetRawBoneNum(); ++BoneIndex)
	{
		Simplygon::spSceneBone SceneBone = SDK->CreateSceneBone();
		BoneArray.Add(SceneBone);
	}

	Simplygon::spSceneBoneTable BoneTable = Scene->GetBoneTable();
	for (int32 BoneIndex = 0; BoneIndex < Skeleton.GetRawBoneNum(); ++BoneIndex)
	{
		int32 ParentIndex = Skeleton.GetParentIndex(BoneIndex);
		Simplygon::spSceneBone CurrentBone = BoneArray[BoneIndex];

		/*if(InBoneTree[BoneIndex].bLockBone)
			{
			CurrentBone->SetLockFromBoneLOD(true);
			}

			if(InBoneTree[BoneIndex].bRemoveBone)
			{
			CurrentBone->SetForceBoneRemoval(true);
			}*/

		// We add the bone to the scene's bone table. This returns a uid that we must apply to the geometry
		// data.
		Simplygon::rid BoneID = BoneTable->AddBone(CurrentBone);
		OutBoneTableIDs.Add(BoneID);

		// Handle root bone. Add to Scene root.
		if (BoneIndex == 0)
		{
			Scene->GetRootNode()->AddChild(CurrentBone);
		}
		else
		{
			Simplygon::spSceneBone ParentBone = BoneArray[ParentIndex];
			ParentBone->AddChild(CurrentBone);
		}
	}
}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryFromSkeletalLODModel(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	const FSkeletalMeshLODModel& LODModel,
	const TArray<Simplygon::rid>& BoneIDs,
	const TArray<FMatrix>& BoneMatrices,
	const int32 /*LODIndex*/)
{
	Simplygon::spGeometryData GeometryData = CreateGeometryFromSkeletalLODModel(
		SDK,
		LODModel,
		BoneIDs,
		BoneMatrices);

	// Create a new simplygon scene mesh object
	Simplygon::spSceneMesh Mesh = SDK->CreateSceneMesh();

	// Assign the geometry data to the mesh
	Mesh->SetGeometry(GeometryData);

	// Add Mesh to the scene
	Scene->GetRootNode()->AddChild(Mesh);

	return GeometryData;
}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryFromSkeletalLODModel(
	Simplygon::ISimplygon* SDK,
	const FSkeletalMeshLODModel& LODModel,
	const TArray<Simplygon::rid>& BoneIDs,
	const TArray<FMatrix>& BoneMatrices,
	const TMap<int32, int32>& SectionIndexToMaterialIndex,
	const TArray<FBox2D>& TextureBounds,
	const TArray<FVector2D>& InTexCoords)
{
	TArray<FSoftSkinVertex> Vertices;
	LODModel.GetVertices(Vertices);

	const uint32 VertexCount = LODModel.NumVertices;
	const uint32 TexCoordCount = LODModel.NumTexCoords;
	check(TexCoordCount <= MAX_TEXCOORDS);

	uint32 TriCount = 0;

	const uint32 SectionCount = (uint32)LODModel.Sections.Num();

	for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
	{
		TriCount += LODModel.Sections[SectionIndex].NumTriangles;
	}

	Simplygon::spGeometryData GeometryData = SDK->CreateGeometryData();
	GeometryData->SetVertexCount(VertexCount);
	GeometryData->SetTriangleCount(TriCount);

	// Per-vertex data.
	GeometryData->AddBoneWeights(MAX_TOTAL_INFLUENCES);
	Simplygon::spRealArray Positions = GeometryData->GetCoords();
	Simplygon::spRidArray BoneIds = GeometryData->GetBoneIds();
	Simplygon::spRealArray BoneWeights = GeometryData->GetBoneWeights();

	// Per-corner per-triangle data.
	Simplygon::spRidArray Indices = GeometryData->GetVertexIds();
	GeometryData->AddNormals();
	GeometryData->AddTangents(0);
	Simplygon::spRealArray Normals = GeometryData->GetNormals();
	Simplygon::spRealArray Tangents = GeometryData->GetTangents(0);
	Simplygon::spRealArray Bitangents = GeometryData->GetBitangents(0);

	Simplygon::spValueArray ColorValues = GeometryData->AddBaseTypeUserCornerField(Simplygon::EBaseTypes::TYPES_ID_UCHAR, SIMPLYGON_COLOR_CHANNEL, 4);
	check(ColorValues);
	Simplygon::spUnsignedCharArray Colors = Simplygon::spUnsignedCharArray::SafeCast(ColorValues);
	check(Colors);

	// Per-triangle data.
	GeometryData->AddMaterialIds();
	Simplygon::spRidArray MaterialIndices = GeometryData->GetMaterialIds();

	// Add per-vertex data. This data needs to be added per-chunk so that we can properly map bones.
	uint32 FirstVertex = 0;
	for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
	{
		const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
		const uint32 LastVertex = FirstVertex + (uint32)Section.SoftVertices.Num();
		for (uint32 VertexIndex = FirstVertex; VertexIndex < LastVertex; ++VertexIndex)
		{
			FSoftSkinVertex& Vertex = Vertices[VertexIndex];
			Simplygon::rid VertexBoneIds[MAX_TOTAL_INFLUENCES];
			Simplygon::real VertexBoneWeights[MAX_TOTAL_INFLUENCES];
			// get blended matrix
			FMatrix BlendedMatrix = FMatrix::Identity;

			uint32 TotalInfluence = 0;
			for (uint32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex)
			{
				int32 BoneIndex = Vertex.InfluenceBones[InfluenceIndex];
				const uint8 BoneInfluence = Vertex.InfluenceWeights[InfluenceIndex];
				TotalInfluence += BoneInfluence;

				if (BoneInfluence > 0)
				{
					check(BoneIndex < Section.BoneMap.Num());
					uint32 BoneID = BoneIDs[Section.BoneMap[BoneIndex]];
					VertexBoneIds[InfluenceIndex] = BoneID;
					VertexBoneWeights[InfluenceIndex] = BoneInfluence / 255.0f;

					if (BoneMatrices.IsValidIndex(Section.BoneMap[BoneIndex]))
					{
						const FMatrix Matrix = BoneMatrices[Section.BoneMap[BoneIndex]];
						// calculate blended matrix
						if (InfluenceIndex == 0)
						{
							BlendedMatrix = (Matrix * VertexBoneWeights[InfluenceIndex]);
						}
						else
						{
							BlendedMatrix += (Matrix * VertexBoneWeights[InfluenceIndex]);
						}
					}
				}
				else
				{
					//Set BoneID and BoneWeight to -1 and 0 respectively as required by simplygon.
					VertexBoneIds[InfluenceIndex] = -1;
					VertexBoneWeights[InfluenceIndex] = 0;
				}
			}

			// transform position
			FVector WeightedVertex = BlendedMatrix.TransformPosition(Vertex.Position);
			FVector WeightedTangentX = BlendedMatrix.TransformVector(Vertex.TangentX);
			FVector WeightedTangentY = BlendedMatrix.TransformVector(Vertex.TangentY);
			FVector WeightedTangentZ = BlendedMatrix.TransformVector(Vertex.TangentZ);

			check(TotalInfluence == 255);
			Vertex.TangentX = WeightedTangentX.GetSafeNormal();
			Vertex.TangentY = WeightedTangentY.GetSafeNormal();
			uint8 WComponent = uint8(Vertex.TangentZ.W);
			Vertex.TangentZ = WeightedTangentZ.GetSafeNormal();
			Vertex.TangentZ.W = WComponent;
			FVector FinalVert = VectorToSimplygon(WeightedVertex);

			//Vertex.Position.Z = -Vertex.Position.Z;
			Positions->SetTuple(VertexIndex, (float*)&FinalVert);
			BoneIds->SetTuple(VertexIndex, VertexBoneIds);
			BoneWeights->SetTuple(VertexIndex, VertexBoneWeights);
		}
		FirstVertex = LastVertex;
	}

	// Add per-vertex per-triangle data.
	for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
	{
		const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
		const uint32 FirstIndex = Section.BaseIndex;
		const uint32 LastIndex = FirstIndex + Section.NumTriangles * 3;

		for (uint32 Index = FirstIndex; Index < LastIndex; ++Index)
		{
			uint32 VertexIndex = LODModel.IndexBuffer[Index];
			FSoftSkinVertex& Vertex = Vertices[VertexIndex];

			FVector Normal = VectorToSimplygon(Vertex.TangentZ);
			FVector Tangent = VectorToSimplygon(Vertex.TangentX);
			FVector Bitangent = VectorToSimplygon(Vertex.TangentY);

			Indices->SetItem(Index, VertexIndex);
			Normals->SetTuple(Index, (float*)&Normal);
			Tangents->SetTuple(Index, (float*)&Tangent);
			Bitangents->SetTuple(Index, (float*)&Bitangent);

			Colors->SetTuple(Index, (UCHAR*)&Vertex.Color);
		}
	}

	auto AddTexCoords = [&GeometryData, &SectionCount, &LODModel, &Vertices](
							int32 TexCoordIndex,
							const TArray<FBox2D>& TextureBounds,
							const TArray<FVector2D>& CustomUVs) {
		bool bUseCustomUVs = (TexCoordIndex == SG_MATERIALBAKING_INDEX && CustomUVs.Num() > 0);

		GeometryData->AddTexCoords(TexCoordIndex);
		Simplygon::spRealArray TexCoords = GeometryData->GetTexCoords(TexCoordIndex);
		check(TexCoords->GetTupleSize() == 2);

		for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
		{
			const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
			const uint32 FirstIndex = Section.BaseIndex;
			const uint32 LastIndex = FirstIndex + Section.NumTriangles * 3;

			// Compute texture bounds for current material.
			float MinU = 0, ScaleU = 1;
			float MinV = 0, ScaleV = 1;
			if (TextureBounds.IsValidIndex(Section.MaterialIndex) && TexCoordIndex == 0 &&
				CustomUVs.Num() == 0)
			{
				const FBox2D& Bounds = TextureBounds[Section.MaterialIndex];
				if (Bounds.GetArea() > 0)
				{
					MinU = float(Bounds.Min.X);
					MinV = float(Bounds.Min.Y);
					ScaleU = 1.0f / float(Bounds.Max.X - Bounds.Min.X);
					ScaleV = 1.0f / float(Bounds.Max.Y - Bounds.Min.Y);
				}
			}

			for (uint32 Index = FirstIndex; Index < LastIndex; ++Index)
			{
				uint32 VertexIndex = LODModel.IndexBuffer[Index];
				FSoftSkinVertex& Vertex = Vertices[VertexIndex];

				const FVector2D& TexCoord = bUseCustomUVs ? CustomUVs[Index] : Vertex.UVs[TexCoordIndex];
				float UV[2];
				UV[0] = (TexCoord.X - MinU) * ScaleU;
				UV[1] = (TexCoord.Y - MinV) * ScaleV;
				TexCoords->SetTuple(Index, UV);
			}
		}
	};

	// Add texture coordinates (Material baking)
	if (InTexCoords.Num() > 0)
		AddTexCoords(SG_MATERIALBAKING_INDEX, TextureBounds, InTexCoords);

	// Add texture coordinates (Original)
	for (uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex)
	{
		AddTexCoords(TexCoordIndex, TextureBounds, InTexCoords);
	}

	// Add per-triangle data.
	uint32 TriangleCount = 0;
	for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
	{
		const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];

		if (Section.NumTriangles == 0)
		{
			UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Skipping section with zero triangles"));
			continue;
		}

		const uint32 FirstTriIndex = Section.BaseIndex / 3;
		const uint32 LastTriIndex = FirstTriIndex + Section.NumTriangles - 1;
		const uint16 MaterialIndex = SectionIndexToMaterialIndex.Contains(SectionIndex) ? uint16(SectionIndexToMaterialIndex[SectionIndex]) : Section.MaterialIndex;
		for (uint32 TriIndex = FirstTriIndex; TriIndex <= LastTriIndex; ++TriIndex)
		{
			MaterialIndices->SetItem(TriangleCount, MaterialIndex);
			check(MaterialIndices->GetItem(TriangleCount) == MaterialIndex);
			TriangleCount++;
		}
	}

#if 0
		if (GetMutableDefault<USimplygonSettings>()->bPreserveOriginalSectionCount)
		{
			TMap<int32, int32> MatIdToTriId;

			Simplygon::spRidArray MaterialIds = GeometryData->GetMaterialIds();
			for (uint32 TriangleId = 0; TriangleId < MaterialIds->GetItemCount(); ++TriangleId)
			{
				int32 MatId = MaterialIds->GetItem(TriangleId);
				if (!MatIdToTriId.Contains(MatId))
				{
					MatIdToTriId.Add(MatId, TriangleId);
				}
			}

			GeometryData->AddVertexLocks();
			Simplygon::spBoolArray VertexLocks = GeometryData->GetVertexLocks();
			Simplygon::spRidArray VertexIds = GeometryData->GetVertexIds();
			for (int32 i = 0; i < MatIdToTriId.Num(); ++i)
			{
				//Use triangle id as an offset into vertex ids.
				int32 Offset = MatIdToTriId[i];
				for (int32 j = 0; j < 3; ++j)
				{
					int32 VertexId = VertexIds->GetItem(Offset * 3 + j);
					VertexLocks->SetItem(VertexId, true);
				}
			}
		}
#endif
	return GeometryData;
}

void FSimplygonDataConverter::CreateSkeletalLODModelFromGeometry(
	FSkeletalMeshLODModel* NewModel,
	const Simplygon::spGeometryData& GeometryData,
	USkeletalMesh* SkeletalMesh,
	const int32 LODIndex,
	const bool bHasBakedMaterials,
	FBoxSphereBounds& OutBounds,
	const bool bCreateMorphTargets,
	const TSet<TSoftObjectPtr<UMorphTarget>>* MorphTargetsToRemove)
{
	check(GeometryData);

	FSkeletalMeshData MeshData;
	ExtractSkeletalDataFromGeometry(GeometryData, MeshData, bHasBakedMaterials);

	TArray<FVector>& Vertices = MeshData.Points;
	for (int32 VertexIndex = 0; VertexIndex < MeshData.Points.Num(); ++VertexIndex)
	{
		Vertices[VertexIndex] = VectorFromSimplygon(Vertices[VertexIndex]);
	}

	OutBounds = FSimplygonDataConverter::CalculateBounds(Vertices);

	for (int32 FaceIndex = 0; FaceIndex < MeshData.Faces.Num(); ++FaceIndex)
	{
		SkeletalMeshImportData::FMeshFace& Face = MeshData.Faces[FaceIndex];
		for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
		{
			Face.TangentX[CornerIndex] = FSimplygonDataConverter::VectorFromSimplygon(Face.TangentX[CornerIndex]);
			Face.TangentY[CornerIndex] = FSimplygonDataConverter::VectorFromSimplygon(Face.TangentY[CornerIndex]);
			Face.TangentZ[CornerIndex] = FSimplygonDataConverter::VectorFromSimplygon(Face.TangentZ[CornerIndex]);
		}
	}

	// Create a map of 'point to original'
	// Needed in order to create morph targets from GeometryData
	TArray<int32> VertexToOriginalVertex;
	VertexToOriginalVertex.AddUninitialized(MeshData.Points.Num());
	for (int32 PointIdx = 0; PointIdx < MeshData.Points.Num(); PointIdx++)
	{
		VertexToOriginalVertex[PointIdx] = PointIdx;
	}

	// Make sure we do not recalculate normals
	IMeshUtilities::MeshBuildOptions Options;
	Options.bComputeNormals = false;
	Options.bComputeTangents = false;
	IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");

	// Create skinning streams for NewModel.
	MeshUtilities.BuildSkeletalMesh(*NewModel, SkeletalMesh->GetName(), SkeletalMesh->GetRefSkeleton(), MeshData.Influences, MeshData.Wedges, MeshData.Faces, MeshData.Points, VertexToOriginalVertex, Options);

	NewModel->SyncronizeUserSectionsDataArray();

	// Set texture coordinate count on the new model.
	NewModel->NumTexCoords = MeshData.TexCoordCount;

	if (bCreateMorphTargets)
	{
		CreateMorphTargetsFromGeometry(SkeletalMesh->GetMorphTargets(), GeometryData, LODIndex, NewModel, MeshData.MorphTargetIgnoreList, MorphTargetsToRemove);
	}
}

static Simplygon::spGeometryData ExtractMeshByMaterialIndex(Simplygon::ISimplygon* API, Simplygon::spGeometryData SourceMesh, int32 MaterialIndex)
{
	Simplygon::spGeometryData TargetMesh = API->CreateGeometryData();
	TargetMesh->DeepCopy(SourceMesh, false);

	uint32 OriginalTriangleCount = SourceMesh->GetTriangleCount();
	uint32 OriginalVertexCount = SourceMesh->GetVertexCount();

	Simplygon::spRidArray OriginalMaterialIds = SourceMesh->GetMaterialIds();
	Simplygon::spRidArray OriginalVertexIds = SourceMesh->GetVertexIds();

	TArray<int32> PerMaterialUniqueVertexIds;
	TArray<int32> PerMaterialCornerIds;
	TArray<int32> PerMaterialTriangleIds;

	for (auto TriangleIndex = 0U; TriangleIndex < OriginalTriangleCount; ++TriangleIndex)
	{
		if (OriginalMaterialIds.GetItem(TriangleIndex) != MaterialIndex)
			continue;

		for (auto CornerIndex = 0U; CornerIndex < 3; ++CornerIndex)
		{
			auto Corner = TriangleIndex * 3 + CornerIndex;
			auto VertexIndex = OriginalVertexIds.GetItem(Corner);

			PerMaterialUniqueVertexIds.AddUnique(VertexIndex);
			PerMaterialCornerIds.Add(Corner);
		}

		PerMaterialTriangleIds.Add(TriangleIndex);
	}

	TargetMesh->SetTriangleCount(PerMaterialTriangleIds.Num());
	TargetMesh->SetVertexCount(PerMaterialUniqueVertexIds.Num());

	Simplygon::spRidArray TriangleIdArray = API->CreateRidArray();
	TriangleIdArray->SetData(PerMaterialTriangleIds.GetData(), PerMaterialTriangleIds.Num());

	Simplygon::spRidArray CornerIdArray = API->CreateRidArray();
	CornerIdArray->SetData(PerMaterialCornerIds.GetData(), PerMaterialCornerIds.Num());

	Simplygon::spRidArray VertexIdArray = API->CreateRidArray();
	VertexIdArray->SetData(PerMaterialUniqueVertexIds.GetData(), PerMaterialUniqueVertexIds.Num());

	TargetMesh->GetTriangles()->IndexedCopy(SourceMesh->GetTriangles(), TriangleIdArray, 0);
	TargetMesh->GetCorners()->IndexedCopy(SourceMesh->GetCorners(), CornerIdArray, 0);
	TargetMesh->GetVertices()->IndexedCopy(SourceMesh->GetVertices(), VertexIdArray, 0);

	Simplygon::spRidArray NewVertexIds = TargetMesh->GetVertexIds();

	PerMaterialUniqueVertexIds.Empty();

	uint32 CurrentTriangleIndex = 0;
	uint32 CurrentCornerindex = 0;

	for (auto TriangleIndex = 0U; TriangleIndex < OriginalTriangleCount; ++TriangleIndex)
	{
		if (OriginalMaterialIds.GetItem(TriangleIndex) != MaterialIndex)
			continue;

		for (auto CornerIndex = 0U; CornerIndex < 3; ++CornerIndex)
		{
			auto Corner = TriangleIndex * 3 + CornerIndex;

			// re-link vertex ids and vertex positions
			auto VertexIndex = OriginalVertexIds.GetItem(Corner);
			auto NewVertexIndex = PerMaterialUniqueVertexIds.AddUnique(VertexIndex);

			NewVertexIds.SetItem(CurrentCornerindex, NewVertexIndex);
			CurrentCornerindex++;
		}
		CurrentTriangleIndex++;
	}

	return TargetMesh;
}

static Simplygon::spGeometryData ExtractGeometryForMaterial(Simplygon::ISimplygon* API, Simplygon::spGeometryData Source, int32 MaterialIndex)
{
	Simplygon::spGeometryData SubSectionGeometry = ExtractMeshByMaterialIndex(API, Source, MaterialIndex);

	for (auto Index = 0U; Index < SubSectionGeometry->GetMaterialIds()->GetItemCount(); ++Index)
	{
		SubSectionGeometry->GetMaterialIds()->SetItem(Index, 0);
	}

	return SubSectionGeometry;
}

void FSimplygonDataConverter::PackChartsUsingChartAggregator(
	Simplygon::ISimplygon* SDK,
	Simplygon::spGeometryData GeometryData,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	uint32 TargetTexCoordIndex,
	FAddMeshMaterialInfoResult& MaterialInfo,
	TArray<FVector2D>& OutTexCoords)
{
	// Find empty texcoord level
	if (GeometryData->GetTexCoords(TargetTexCoordIndex).IsNull())
	{
		GeometryData->AddTexCoords(TargetTexCoordIndex);
		Simplygon::spRealArray TexCoords = GeometryData->GetTexCoords(TargetTexCoordIndex);

		// fill empty field with zeroes
		for (auto CornerIndex = 0U; CornerIndex < TexCoords.GetItemCount(); ++CornerIndex)
		{
			TexCoords.SetItem(CornerIndex, 0.f);
		}
	}

	TArray<int32> UniqueMaterialIds;
	Simplygon::spRidArray MaterialIds = GeometryData->GetMaterialIds();
	auto NumMaterialIds = MaterialIds->GetItemCount();
	for (auto MatIndex = 0U; MatIndex < NumMaterialIds; ++MatIndex)
	{
		UniqueMaterialIds.AddUnique(MaterialIds->GetItem(MatIndex));
	}

	bool bSuccess = false;
	Simplygon::spMaterialTable MaterialTable = SDK->CreateMaterialTable();
	Simplygon::spChartAggregator ChartAggregator = SDK->CreateChartAggregator();

	ChartAggregator->SetChartAggregatorMode((Simplygon::EChartAggregatorMode)FlattenMaterialSettings.ChartAggregatorSettings.ChartAggregatorMode);
	ChartAggregator->SetSurfaceAreaScale((Simplygon::ESurfaceAreaScale)FlattenMaterialSettings.ChartAggregatorSettings.SurfaceAreaScale);
	ChartAggregator->SetLockUVRotation(FlattenMaterialSettings.ChartAggregatorSettings.LockUVRotation);
	ChartAggregator->SetTexCoordLevel(0);
	ChartAggregator->SetUseVertexWeights(false);
	ChartAggregator->SetTextureWidth(FlattenMaterialSettings.TextureSize.X);
	ChartAggregator->SetTextureHeight(FlattenMaterialSettings.TextureSize.Y);
	ChartAggregator->SetGutterSpace(unsigned int(FlattenMaterialSettings.GutterSpace));

	// setup user fields for tracking and remapping
	Simplygon::spRidArray OriginalCornerIds = Simplygon::spRidArray::SafeCast(
		GeometryData->AddBaseTypeUserCornerField(Simplygon::EBaseTypes::TYPES_ID_RID, SG_USER_CORNER_FIELD_CORNERIDS, 1));

	for (auto TriangleIndex = 0U; TriangleIndex < GeometryData->GetTriangleCount(); ++TriangleIndex)
	{
		OriginalCornerIds->SetItem(TriangleIndex * 3 + 0, TriangleIndex * 3 + 0);
		OriginalCornerIds->SetItem(TriangleIndex * 3 + 1, TriangleIndex * 3 + 1);
		OriginalCornerIds->SetItem(TriangleIndex * 3 + 2, TriangleIndex * 3 + 2);
	}

	Simplygon::spRidArray OriginalTriangleIds = Simplygon::spRidArray::SafeCast(
		GeometryData->AddBaseTypeUserTriangleField(Simplygon::EBaseTypes::TYPES_ID_RID, "SG_USER_TRIANGLE_FIELD_TRIANGLEIDS", 1));

	for (auto TriangleIndex = 0U; TriangleIndex < GeometryData->GetTriangleCount(); ++TriangleIndex)
	{
		OriginalTriangleIds->SetItem(TriangleIndex, TriangleIndex);
	}

	Simplygon::spGeometryData MergedGeometry = SDK->CreateGeometryData();

	// Note: We split the geometry and repack the charts to get better texture usage.
	// As the flatten material process only takes the sub geometry into account.
	for (auto MaterialId : UniqueMaterialIds)
	{
		MaterialTable->Clear();
		Simplygon::spMaterial material = SDK->CreateMaterial();
		MaterialTable->AddMaterial(material);

		Simplygon::spGeometryData SubGeometry = ExtractGeometryForMaterial(SDK, GeometryData, MaterialId);

		ChartAggregator->SetSeparateOverlappingCharts(MaterialInfo.MaterialHasVertexData[MaterialId]);
		ChartAggregator->SetMaterialTable(MaterialTable);
		bSuccess = ChartAggregator->Parameterize(SubGeometry, SubGeometry->GetTexCoords(TargetTexCoordIndex));

		if (!bSuccess)
		{
			UE_LOG(LogSimplygonDataConverter, Error, TEXT("Failed to create a valid Simplygon TexCoordGenerator."));
			return;
		}

		MergedGeometry->AppendGeometry(SubGeometry);
	}

	//fetch the cornerids for remapping
	OriginalCornerIds = Simplygon::spRidArray::SafeCast(MergedGeometry->GetUserCornerField(SG_USER_CORNER_FIELD_CORNERIDS));
	OriginalTriangleIds = Simplygon::spRidArray::SafeCast(MergedGeometry->GetUserTriangleField("SG_USER_TRIANGLE_FIELD_TRIANGLEIDS"));

	if (OriginalCornerIds.IsNull())
	{
		UE_LOG(LogSimplygonDataConverter, Error, TEXT("Remapping UVs to original geometry requires original corner ids."));
		return;
	}

	GeometryData->GetTexCoords(TargetTexCoordIndex)->IndexedCopy(MergedGeometry->GetTexCoords(TargetTexCoordIndex), OriginalCornerIds, 0);

	auto NumCorners = GeometryData->GetTriangleCount() * 3;

	OutTexCoords.Empty(NumCorners);
	OutTexCoords.AddUninitialized(NumCorners);

	Simplygon::spRealArray NewTexcoords = GeometryData->GetTexCoords(TargetTexCoordIndex);
	for (auto WedgeIndex = 0U; WedgeIndex < NumCorners; ++WedgeIndex)
	{
		Simplygon::spRealData DestTexCoord = NewTexcoords->GetTuple(WedgeIndex);
		const Simplygon::real* sgTexCoords = DestTexCoord.Data();

		OutTexCoords[WedgeIndex].X = sgTexCoords[0];
		OutTexCoords[WedgeIndex].Y = sgTexCoords[1];
	}
}

void FSimplygonDataConverter::GenerateTextureCoordinates(
	Simplygon::ISimplygon* SDK,
	Simplygon::spGeometryData GeometryData,
	uint32 TexCoordIndex,
	Simplygon::spObject TextureCoordinateGenerator,
	TArray<FVector2D>& OutTexCoords)
{

	// Find empty texcoord level
	if (GeometryData->GetTexCoords(TexCoordIndex).IsNull())
	{
		GeometryData->AddTexCoords(TexCoordIndex);
	}

	TArray<int32> UniqueMaterialIds;
	Simplygon::spRidArray MaterialIds = GeometryData->GetMaterialIds();
	int32 NumMaterialIds = MaterialIds->GetItemCount();
	for (int32 MatIndex = 0; MatIndex < NumMaterialIds; ++MatIndex)
	{
		UniqueMaterialIds.AddUnique(MaterialIds->GetItem(MatIndex));
	}

	bool bSuccess = false;
	int32 NumDummyMaterialToAdd = FMath::Max(UniqueMaterialIds) + 1;
	Simplygon::spMaterialTable MaterialTable = SDK->CreateMaterialTable();

	for (int32 MaterialIndex = 0; MaterialIndex < NumDummyMaterialToAdd; ++MaterialIndex)
	{
		Simplygon::spMaterial material = SDK->CreateMaterial();
		MaterialTable->AddMaterial(material);
	}

	Simplygon::spChartAggregator ChartAggregator = Simplygon::spChartAggregator::SafeCast(TextureCoordinateGenerator);
	Simplygon::spParameterizer Paramaterizer = Simplygon::spParameterizer::SafeCast(TextureCoordinateGenerator);
	check(ChartAggregator || Paramaterizer)

		if (ChartAggregator)
	{
		ChartAggregator->SetMaterialTable(MaterialTable);
		bSuccess = ChartAggregator->Parameterize(GeometryData, GeometryData->GetTexCoords(TexCoordIndex));
	}
	else if (Paramaterizer) { bSuccess = Paramaterizer->Parameterize(GeometryData, GeometryData->GetTexCoords(TexCoordIndex)); }

	if (!bSuccess)
	{
		return;
	}

	int32 NumCorners = GeometryData->GetTriangleCount() * 3;

	OutTexCoords.Empty(NumCorners);
	OutTexCoords.AddUninitialized(NumCorners);
	Simplygon::spRealArray NewTexcoords = GeometryData->GetTexCoords(TexCoordIndex);
	for (int32 WedgeIndex = 0; WedgeIndex < NumCorners; ++WedgeIndex)
	{
		Simplygon::spRealData DestTexCoord = NewTexcoords->GetTuple(WedgeIndex);
		const Simplygon::real* sgTexCoords = DestTexCoord.Data();

		OutTexCoords[WedgeIndex].X = sgTexCoords[0];
		OutTexCoords[WedgeIndex].Y = sgTexCoords[1];
	}
}

static Simplygon::spImageData GetImageData(
	const Simplygon::spMaterial& InSGMaterial,
	const char* SGMaterialChannelName,
	const Simplygon::spTextureTable& TextureTable)
{
	if (!InSGMaterial->HasMaterialChannel(SGMaterialChannelName))
	{
		return Simplygon::NullPtr;
	}

	Simplygon::spShadingNode ExitNode = InSGMaterial->GetShadingNetwork(SGMaterialChannelName);
	if (ExitNode.IsNull())
	{
		// Current channel has not been baked
		return Simplygon::NullPtr;
	}

	Simplygon::spShadingColorNode ColorNode = Simplygon::spShadingColorNode::SafeCast(ExitNode);
	if (!ColorNode.IsNull())
	{
		UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Baked Material has ColorNode on %s channel."), ANSI_TO_TCHAR(SGMaterialChannelName));
		return Simplygon::NullPtr;
	}

	Simplygon::spShadingTextureNode TextureNode = Simplygon::spShadingTextureNode::SafeCast(ExitNode);
	if (TextureNode.IsNull())
	{
		// Current channel is not a texture
		UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Baked Material has no texture for %s channel."), ANSI_TO_TCHAR(SGMaterialChannelName));
		return Simplygon::NullPtr;
	}
	// Names are based on guid, so this should be safe...
	Simplygon::rid TextureId = TextureTable->FindTextureId(TextureNode->GetTextureName());
	Simplygon::spTexture Texture = TextureTable->GetTexture(TextureId);
	if (!Texture)
	{
		// Did not find texture
		return Simplygon::NullPtr;
	}

	Simplygon::spImageData TextureImageData = Texture->GetImageData();
	if (TextureImageData.IsNull())
	{
		Simplygon::NullPtr;
	}

	return TextureImageData;
}

void FSimplygonDataConverter::GetMaterialChannelData(
	const Simplygon::spMaterial& InSGMaterial,
	const Simplygon::spTextureTable& TextureTable,
	const char* SGMaterialChannelName,
	TArray<FColor>& OutSamples,
	FIntPoint& OutTextureSize,
	float& OutEmissiveScale,
	TArray<FFloat16Color>* OutSamples16)
{
	Simplygon::spImageData TextureImageData = GetImageData(InSGMaterial, SGMaterialChannelName, TextureTable);
	if (TextureImageData.IsNull())
		return;

	EFlattenMaterialProperties FlattenProperty;
	FSimplygonMaterialChannelConverter::MaterialChannelToProperty(SGMaterialChannelName, FlattenProperty);

	OutTextureSize.X = TextureImageData->GetXSize();
	OutTextureSize.Y = TextureImageData->GetYSize();

	int32 TexelsCount = OutTextureSize.X * OutTextureSize.Y;
	OutSamples.Empty(TexelsCount);
	OutSamples.AddUninitialized(TexelsCount);

	if (OutSamples16)
	{
		OutSamples16->Empty(TexelsCount);
	}

	auto HasAlphaChannel = [&SGMaterialChannelName, &FlattenProperty]() {
		return SGMaterialChannelName == Simplygon::SG_MATERIAL_CHANNEL_DIFFUSE ||
			   SGMaterialChannelName == Simplygon::SG_MATERIAL_CHANNEL_BASECOLOR ||
			   FlattenProperty == EFlattenMaterialProperties::Diffuse;
	};

	if (FlattenProperty == EFlattenMaterialProperties::Emissive)
	{
		Simplygon::spUnsignedShortArray ImageColors = Simplygon::spUnsignedShortArray::SafeCast(TextureImageData->GetColors());

		Simplygon::spUnsignedShortData ImageData = ImageColors->GetData();
		const uint16* ColorData = ImageData.Data();
		const unsigned int TupleSize = ImageColors->GetTupleSize();
		float MaxValue = 0.0f;
		for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData += TupleSize)
			MaxValue = FMath::Max(MaxValue, FMath::Max3((float)ColorData[0], (float)ColorData[1], (float)ColorData[2]));
		OutEmissiveScale = MaxValue / 255.0f;

		float Scale = 255.0f / MaxValue;
		ColorData = ImageData.Data();
		for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData += TupleSize)
		{
			OutSamples[TexelIndex].R = (uint8)FMath::RoundToInt((float)ColorData[0] * Scale);
			OutSamples[TexelIndex].G = (uint8)FMath::RoundToInt((float)ColorData[1] * Scale);
			OutSamples[TexelIndex].B = (uint8)FMath::RoundToInt((float)ColorData[2] * Scale);
		}
	}
	else
	{
		switch (TextureImageData->GetColorsValueType())
		{
		// 16-bit
		case Simplygon::EBaseTypes::TYPES_ID_USHORT:
		{
			OutSamples16->AddUninitialized(TexelsCount);
			Simplygon::spUnsignedShortArray ImageColors16 = Simplygon::spUnsignedShortArray::SafeCast(TextureImageData->GetColors());
			Simplygon::spUnsignedShortData ImageData16 = ImageColors16->GetData();
			const uint16* ColorData16 = ImageData16.Data();
			const unsigned int TupleSize = ImageColors16->GetTupleSize();

			for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData16 += TupleSize)
			{
				(*OutSamples16)[TexelIndex].R = (float)ColorData16[0] / (float)UINT16_MAX;
				(*OutSamples16)[TexelIndex].G = (float)ColorData16[1] / (float)UINT16_MAX;
				(*OutSamples16)[TexelIndex].B = (float)ColorData16[2] / (float)UINT16_MAX;
				(*OutSamples16)[TexelIndex].A = HasAlphaChannel() ? ((float)ColorData16[3] / (float)UINT16_MAX) : FLinearColor::White.A;
			}
			break;
		}
		// 32-bit
		case Simplygon::EBaseTypes::TYPES_ID_FLOAT:
		{
			OutSamples16->AddUninitialized(TexelsCount);
			Simplygon::spFloatArray ImageColors32 = Simplygon::spFloatArray::SafeCast(TextureImageData->GetColors());
			Simplygon::spFloatData ImageData32 = ImageColors32->GetData();
			const float* ColorData32 = ImageData32.Data();
			const unsigned int TupleSize = ImageColors32->GetTupleSize();

			for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData32 += TupleSize)
			{
				(*OutSamples16)[TexelIndex].R = ColorData32[0];
				(*OutSamples16)[TexelIndex].G = ColorData32[1];
				(*OutSamples16)[TexelIndex].B = ColorData32[2];
				(*OutSamples16)[TexelIndex].A = HasAlphaChannel() ? ColorData32[3] : FLinearColor::White.A;
			}
			break;
		}
		// 8-bit
		default:
		{
			Simplygon::spUnsignedCharArray ImageColors8 = Simplygon::spUnsignedCharArray::SafeCast(TextureImageData->GetColors());
			Simplygon::spUnsignedCharData ImageData8 = ImageColors8->GetData();
			const uint8* ColorData8 = ImageData8.Data();
			const unsigned int TupleSize = ImageColors8->GetTupleSize();
			for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData8 += TupleSize)
			{
				OutSamples[TexelIndex].R = ColorData8[0];
				OutSamples[TexelIndex].G = ColorData8[1];
				OutSamples[TexelIndex].B = ColorData8[2];
				OutSamples[TexelIndex].A = HasAlphaChannel() ? ColorData8[3] : FColor::White.A;
			}
			break;
		}
		}
	}
}

void FSimplygonDataConverter::SetMaterialChannelData(
	Simplygon::ISimplygon* SDK,
	const TArray<FColor>& InSamples,
	FIntPoint InTextureSize,
	Simplygon::spMaterial& SgMaterial,
	Simplygon::spTextureTable& TextureTable,
	FCriticalSection& TableMutex,
	const char* MaterialChannelName,
	bool sRGB,
	float EmissiveScale)
{
	// Add material channel to material
	if (!SgMaterial->HasMaterialChannel(MaterialChannelName))
		SgMaterial->AddMaterialChannel(MaterialChannelName);

	if (InSamples.Num() > 1)
	{
		EFlattenMaterialProperties FlattenProperty;
		FSimplygonMaterialChannelConverter::MaterialChannelToProperty(MaterialChannelName, FlattenProperty);

		int32 NumTexels = InTextureSize.X * InTextureSize.Y;
		Simplygon::spImageData ImageData = SDK->CreateImageData();

		auto HasAlphaChannel = [&MaterialChannelName, &FlattenProperty]() {
			return MaterialChannelName == Simplygon::SG_MATERIAL_CHANNEL_DIFFUSE ||
				   MaterialChannelName == Simplygon::SG_MATERIAL_CHANNEL_BASECOLOR ||
				   FlattenProperty == EFlattenMaterialProperties::Diffuse;
		};

		// 16bit color channel for Emissive so that we can recalculate emissive scale after casting
		if (FlattenProperty == EFlattenMaterialProperties::Emissive)
		{
			ImageData->AddColors(Simplygon::EBaseTypes::TYPES_ID_USHORT, Simplygon::SG_IMAGEDATA_FORMAT_RGB);
			ImageData->Set2DSize(InTextureSize.X, InTextureSize.Y);
			Simplygon::spUnsignedShortArray ImageColors = Simplygon::spUnsignedShortArray::SafeCast(ImageData->GetColors());

			ImageColors->SetTupleCount(NumTexels);
			check(NumTexels - 1 <= ImageColors->GetMaxTupleId())

				uint16* SwizzledBuffer = new uint16[NumTexels * 3];
			uint16* Texel = SwizzledBuffer;
			for (int32 TexelIndex = 0; TexelIndex < NumTexels; TexelIndex++)
			{
				*Texel++ = (uint16)FMath::RoundToInt((float)InSamples[TexelIndex].R * EmissiveScale);
				*Texel++ = (uint16)FMath::RoundToInt((float)InSamples[TexelIndex].G * EmissiveScale);
				*Texel++ = (uint16)FMath::RoundToInt((float)InSamples[TexelIndex].B * EmissiveScale);
			}

			ImageColors->SetData(SwizzledBuffer, NumTexels * 3);
			delete[] SwizzledBuffer;
		}
		else if (HasAlphaChannel())
		{
			ImageData->AddColors(Simplygon::EBaseTypes::TYPES_ID_UCHAR, Simplygon::SG_IMAGEDATA_FORMAT_RGBA);
			ImageData->Set2DSize(InTextureSize.X, InTextureSize.Y);
			Simplygon::spUnsignedCharArray ImageColors = Simplygon::spUnsignedCharArray::SafeCast(ImageData->GetColors());

			ImageColors->SetTupleCount(NumTexels);
			check(NumTexels - 1 <= ImageColors->GetMaxTupleId())

				uint8* SwizzledBuffer = new uint8[NumTexels * 4];
			uint8* Texel = SwizzledBuffer;
			for (int32 TexelIndex = 0; TexelIndex < NumTexels; TexelIndex++)
			{
				*Texel++ = InSamples[TexelIndex].R;
				*Texel++ = InSamples[TexelIndex].G;
				*Texel++ = InSamples[TexelIndex].B;
				*Texel++ = InSamples[TexelIndex].A;
			}

			ImageColors->SetData(SwizzledBuffer, NumTexels * 4);
			delete[] SwizzledBuffer;
		}
		else
		{
			ImageData->AddColors(Simplygon::EBaseTypes::TYPES_ID_UCHAR, Simplygon::SG_IMAGEDATA_FORMAT_RGB);
			ImageData->Set2DSize(InTextureSize.X, InTextureSize.Y);
			Simplygon::spUnsignedCharArray ImageColors = Simplygon::spUnsignedCharArray::SafeCast(ImageData->GetColors());

			ImageColors->SetTupleCount(NumTexels);
			check(NumTexels - 1 <= ImageColors->GetMaxTupleId())

				uint8* SwizzledBuffer = new uint8[NumTexels * 3];
			uint8* Texel = SwizzledBuffer;
			for (int32 TexelIndex = 0; TexelIndex < NumTexels; TexelIndex++)
			{
				*Texel++ = InSamples[TexelIndex].R;
				*Texel++ = InSamples[TexelIndex].G;
				*Texel++ = InSamples[TexelIndex].B;
			}

			ImageColors->SetData(SwizzledBuffer, NumTexels * 3);
			delete[] SwizzledBuffer;
		}

		// Need to add a texture to the texture table in order to be able
		// to reference it in the shading network.
		FGuid TextureGuid = FGuid::NewGuid();
		Simplygon::spTexture Texture = SDK->CreateTexture();
#ifndef DEBUG_PROXY_MESH
		Texture->SetImageData(ImageData);
#else
		// Texture->SetFilePath(TCHAR_TO_ANSI(*TextureOutputPath));
#endif
		Texture->SetName(TCHAR_TO_ANSI(*TextureGuid.ToString()));
		TableMutex.Lock();
		TextureTable->AddTexture(Texture);
		TableMutex.Unlock();

		// Create shading network with one texture node
		Simplygon::spShadingTextureNode TextureNode = SDK->CreateShadingTextureNode();
		TextureNode->SetTexCoordLevel(SG_MATERIALBAKING_INDEX);
		TextureNode->SetTextureName(TCHAR_TO_ANSI(*TextureGuid.ToString()));
		TextureNode->SetColorSpaceOverride(sRGB ? Simplygon::EImageColorSpace::sRGB : Simplygon::EImageColorSpace::Linear);
		SgMaterial->SetShadingNetwork(MaterialChannelName, TextureNode);
	}
	else if (InSamples.Num() == 1)
	{
		Simplygon::spShadingColorNode ColorNode = SDK->CreateShadingColorNode();
		// Handle uniform value
		if (sRGB)
		{
			const FLinearColor& LinearColor = FLinearColor::FromSRGBColor(InSamples[0]);

			ColorNode->SetColor(LinearColor.R, LinearColor.G, LinearColor.B, 1.0f);
		}
		else
		{
			ColorNode->SetColor(InSamples[0].R / 255.f, InSamples[0].G / 255.f, InSamples[0].B / 255.f, 1.0f);
		}

		SgMaterial->SetShadingNetwork(MaterialChannelName, ColorNode);
	}
	else
	{
		Simplygon::spShadingColorNode ColorNode = SDK->CreateShadingColorNode();
		ColorNode->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
		SgMaterial->SetShadingNetwork(MaterialChannelName, ColorNode);
	}
}

void FSimplygonDataConverter::ExtractSkeletalDataFromGeometry(const Simplygon::spGeometryData& GeometryData, FSkeletalMeshData& MeshData, bool bHasBakedMaterials)
{
	TArray<FVector> PointNormals;
	TArray<uint32> PointList;
	TArray<uint32> PointInfluenceMap;

	check(GeometryData);

	Simplygon::spRealArray Positions = GeometryData->GetCoords();
	Simplygon::spRidArray Indices = GeometryData->GetVertexIds();
	Simplygon::spRidArray MaterialIndices = GeometryData->GetMaterialIds();
	Simplygon::spValueArray VertexColorValues = GeometryData->GetUserCornerField(SIMPLYGON_COLOR_CHANNEL);
	Simplygon::spUnsignedCharArray VertexColors = Simplygon::spUnsignedCharArray::SafeCast(VertexColorValues);
	Simplygon::spRealArray Normals = GeometryData->GetNormals();
	Simplygon::spRealArray Tangents = bHasBakedMaterials ? GeometryData->GetTangents(SG_MATERIALBAKING_INDEX) : GeometryData->GetTangents(0);
	Simplygon::spRealArray Bitangents = bHasBakedMaterials ? GeometryData->GetBitangents(SG_MATERIALBAKING_INDEX) : GeometryData->GetBitangents(0);
	Simplygon::spRidArray BoneIds = GeometryData->GetBoneIds();
	Simplygon::spRealArray BoneWeights = GeometryData->GetBoneWeights();
	Simplygon::spRealArray TexCoords[MAX_TEXCOORDS];
	Simplygon::spRealArray CustomTexCoordsFromFlatten = GeometryData->GetTexCoords(SG_MATERIALBAKING_INDEX);
	bool bHasCustomUVs = bHasBakedMaterials && CustomTexCoordsFromFlatten != nullptr;

	uint32 TexCoordCount = 0;
	for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_TEXCOORDS; ++TexCoordIndex)
	{
		if (bHasCustomUVs && TexCoordIndex == 0)
			TexCoords[TexCoordIndex] = CustomTexCoordsFromFlatten;
		else
			TexCoords[TexCoordIndex] = GeometryData->GetTexCoords(TexCoordIndex);

		if (TexCoords[TexCoordIndex].IsNull())
			break;
		TexCoordCount++;
	}
	MeshData.TexCoordCount = TexCoordCount;

	check(Positions);
	check(Indices);
	check(Normals);
	check(Tangents);
	check(Bitangents);
	check(BoneIds);
	check(BoneWeights);

	const bool bHaveColors = (!VertexColors.IsNull());
	const uint32 VertexCount = GeometryData->GetVertexCount();
	const uint32 TriCount = GeometryData->GetTriangleCount();

	// Initialize the lists of points, wedges, and faces.
	MeshData.Points.AddZeroed(VertexCount);
	PointNormals.AddZeroed(VertexCount);
	PointList.Reserve(VertexCount);
	PointInfluenceMap.Reserve(VertexCount);
	for (uint32 PointIndex = 0; PointIndex < VertexCount; ++PointIndex)
	{
		PointList.Add(uint32(INDEX_NONE));
		PointInfluenceMap.Add(uint32(INDEX_NONE));
	}
	MeshData.Wedges.AddZeroed(TriCount * 3);
	MeshData.Faces.AddZeroed(TriCount);

	// The number of influences may have changed if we have specified a max number of bones per vertex.
	uint32 NumOfInfluences = FMath::Min<uint32>(BoneIds->GetTupleSize(), MAX_TOTAL_INFLUENCES);

	check(BoneWeights->GetTupleCount() == VertexCount);

	// Per-vertex data.
	for (uint32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex)
	{
		FVector& Point = MeshData.Points[VertexIndex];

		Simplygon::spRealData sgPositionData = Positions->GetTuple(VertexIndex);
		const Simplygon::real* sgPosition = sgPositionData.Data();
		Point = FVector(sgPosition[0], sgPosition[1], sgPosition[2]);

		Simplygon::spRidData sgBoneIdsData = BoneIds->GetTuple(VertexIndex);
		const Simplygon::rid* sgBoneId = sgBoneIdsData.Data();

		Simplygon::spRealData sgBoneWeightsData = BoneWeights->GetTuple(VertexIndex);
		const Simplygon::real* sgBoneWeights = sgBoneWeightsData.Data();

		PointInfluenceMap[VertexIndex] = (uint32)MeshData.Influences.Num();
		for (uint32 InfluenceIndex = 0; InfluenceIndex < NumOfInfluences; ++InfluenceIndex)
		{
			const uint16 BoneIndex = uint16(sgBoneId[InfluenceIndex]);
			const float BoneWeight = sgBoneWeights[InfluenceIndex];

			if (BoneIndex >= 0 && BoneWeight > 0.0f)
			{
				SkeletalMeshImportData::FVertInfluence* VertInfluence =
					new (MeshData.Influences) SkeletalMeshImportData::FVertInfluence;
				VertInfluence->BoneIndex = BoneIndex;
				VertInfluence->Weight = BoneWeight;
				VertInfluence->VertIndex = VertexIndex;
			}
		}
	}

	// Per-triangle and per-corner data.
	for (uint32 TriIndex = 0; TriIndex < TriCount; ++TriIndex)
	{
		// Per-triangle data.
		SkeletalMeshImportData::FMeshFace& Face = MeshData.Faces[TriIndex];

		Face.MeshMaterialIndex = MaterialIndices ? uint16(MaterialIndices->GetItem(TriIndex)) : 0;

		// Per-corner data.
		for (uint32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
		{
			const uint32 WedgeIndex = TriIndex * 3 + CornerIndex;
			const uint32 BasePointIndex = (uint32)Indices->GetItem(WedgeIndex);
			uint32 PointIndex = BasePointIndex;

			check(BasePointIndex < (uint32)PointList.Num());

			// Duplicate points where needed to create hard edges.
			FVector WedgeNormal;
			Simplygon::spRealData sgNormalData = Normals->GetTuple(WedgeIndex);
			const Simplygon::real* sgNormal = sgNormalData.Data();
			WedgeNormal = FVector(sgNormal[0], sgNormal[1], sgNormal[2]);
			WedgeNormal.Normalize();

			FVector WedgeTangent, WedgeBitangent;
			Simplygon::spRealData sgTangentData = Tangents->GetTuple(WedgeIndex);
			const Simplygon::real* sgTangent = sgTangentData.Data();
			WedgeTangent = FVector(sgTangent[0], sgTangent[1], sgTangent[2]);

			Simplygon::spRealData sgBitangentData = Bitangents->GetTuple(WedgeIndex);
			const Simplygon::real* sgBitangent = sgBitangentData.Data();
			WedgeBitangent = FVector(sgBitangent[0], sgBitangent[1], sgBitangent[2]);

			Face.TangentX[CornerIndex] = WedgeTangent;
			Face.TangentY[CornerIndex] = WedgeBitangent;
			Face.TangentZ[CornerIndex] = WedgeNormal;

#if USE_DEPRECATED_CUSTOM_SKMESH_HARDEDGE
			FVector PointNormal = PointNormals[PointIndex];
			if (PointNormal.SizeSquared() < KINDA_SMALL_NUMBER)
			{
				PointNormals[PointIndex] = WedgeNormal;
			}
			else
			{
				while ((PointNormal | WedgeNormal) - 1.0f < -KINDA_SMALL_NUMBER)
				{
					PointIndex = PointList[PointIndex];
					if (PointIndex == INDEX_NONE)
					{
						break;
					}
					check(PointIndex < (uint32)PointList.Num());
					PointNormal = PointNormals[PointIndex];
				}

				if (PointIndex == INDEX_NONE)
				{
					FVector Point = MeshData.Points[BasePointIndex];
					PointIndex = MeshData.Points.Add(Point);
					check(PointIndex == (uint32)PointList.Num());
					check(PointIndex == (uint32)PointInfluenceMap.Num());
					check(PointIndex == (uint32)PointNormals.Num());

					// This point should not be taking into account when re-applying morph targets to LOD,
					// since now the MeshData point count will missmatch GeometryData vertex count.
					MeshData.MorphTargetIgnoreList.Add(PointIndex);

					PointNormals.Add(WedgeNormal);
					uint32 NextPointIndex = PointList[BasePointIndex];
					check(NextPointIndex < (uint32)PointList.Num() || NextPointIndex == INDEX_NONE);
					PointList[BasePointIndex] = PointIndex;
					PointList.Add(NextPointIndex);
					PointInfluenceMap.Add((uint32)MeshData.Influences.Num());

					int32 InfluenceIndex = PointInfluenceMap[BasePointIndex];
					while (MeshData.Influences[InfluenceIndex].VertIndex == BasePointIndex)
					{
						SkeletalMeshImportData::FVertInfluence* NewVertInfluence =
							new (MeshData.Influences) SkeletalMeshImportData::FVertInfluence;
						NewVertInfluence->BoneIndex = MeshData.Influences[InfluenceIndex].BoneIndex;
						NewVertInfluence->Weight = MeshData.Influences[InfluenceIndex].Weight;
						NewVertInfluence->VertIndex = PointIndex;
						InfluenceIndex++;
					}

					check(PointNormals.Num() == MeshData.Points.Num());
					check(PointList.Num() == MeshData.Points.Num());
					check(PointInfluenceMap.Num() == MeshData.Points.Num());
				}
			}
#endif

			check(PointIndex != INDEX_NONE);
			check((MeshData.Points[PointIndex] - MeshData.Points[BasePointIndex]).SizeSquared() ==
				  0.0f);

			SkeletalMeshImportData::FMeshWedge& Wedge = MeshData.Wedges[WedgeIndex];
			Wedge.iVertex = PointIndex;
			for (uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex)
			{
				Simplygon::spRealData sgTexCoordData = TexCoords[TexCoordIndex]->GetTuple(WedgeIndex);
				Wedge.UVs[TexCoordIndex] = FVector2D(sgTexCoordData[0], sgTexCoordData[1]);
			}

			if (bHaveColors)
			{
				Simplygon::spUnsignedCharData sgVertexColorData = VertexColors->GetTuple(WedgeIndex);
				const uint8* sgColors = sgVertexColorData.Data();
				Wedge.Color = FColor(sgColors[2], sgColors[1], sgColors[0], sgColors[3]);
			}
			else
			{
				Wedge.Color = FColor(255, 255, 255, 255);
			}

			Face.iWedge[CornerIndex] = WedgeIndex;
		}
	}
}

void FSimplygonDataConverter::CreateMorphFieldsFromSkeletalMesh(Simplygon::spGeometryData& GeometryData, const USkeletalMesh* SkeletalMesh, int32 BaseLODModel)
{
	if (SkeletalMesh->GetMorphTargets().Num() == 0)
	{
		return;
	}

	// Obtain vertices of original LODModel
	FSkeletalMeshModel* SkeletalMeshResource = SkeletalMesh->GetImportedModel();
	const FSkeletalMeshLODModel& BaseSourceLODModel = SkeletalMeshResource->LODModels[BaseLODModel];

	uint32 NumVertices = BaseSourceLODModel.NumVertices;

	// Compute maximal morph distance for each vertex
	TArray<float> MaxMorphWeight;
	MaxMorphWeight.AddZeroed(NumVertices);
	for (int32 MorphIndex = 0; MorphIndex < SkeletalMesh->GetMorphTargets().Num(); MorphIndex++)
	{
		UMorphTarget* MorphTarget = SkeletalMesh->GetMorphTargets()[MorphIndex];
		if (MorphTarget)
		{

			int32 NumDeltas;
			const FMorphTargetDelta* Deltas = MorphTarget->GetMorphTargetDelta(BaseLODModel, NumDeltas);

			for (int32 MorphVertIdx = 0; MorphVertIdx < NumDeltas; MorphVertIdx++)
			{
				const FMorphTargetDelta& MorphVertex = Deltas[MorphVertIdx];
				float Distance = MorphVertex.PositionDelta.Size();
				int32 WedgeIndex = MorphVertex.SourceIdx;
				if (Distance > MaxMorphWeight[WedgeIndex])
				{
					MaxMorphWeight[WedgeIndex] = Distance;
				}
			}
		}
	}

	// Add vertex weights taking into account morph data
	GeometryData->AddVertexWeights();
	Simplygon::spRealArray VertexWeights = GeometryData->GetVertexWeights();
	for (uint32 Index = 0; Index < NumVertices; Index++)
	{
		// Add vertex weight. 1.0 is default (normal) weight.
		VertexWeights->SetItem(Index, 1.0f + MaxMorphWeight[Index]);
	}

	for (int32 MorphIndex = 0; MorphIndex < SkeletalMesh->GetMorphTargets().Num(); MorphIndex++)
	{
		UMorphTarget* MorphTarget = SkeletalMesh->GetMorphTargets()[MorphIndex];
		if (MorphTarget)
		{
			// Get morph geometry
			int32 NumDeltas;
			const FMorphTargetDelta* Deltas = MorphTarget->GetMorphTargetDelta(BaseLODModel, NumDeltas);

			// Add vertex streams to GeometryData
			FString MorphVertexFieldName = FString::Printf(TEXT("MorphVertexField%d"), MorphIndex);
			FString MorphNormalFieldName = FString::Printf(TEXT("MorphNormalField%d"), MorphIndex);

			Simplygon::spRealArray MorphVertexField = Simplygon::spRealArray::SafeCast(GeometryData->AddBaseTypeUserVertexField(Simplygon::EBaseTypes::TYPES_ID_REAL, TCHAR_TO_ANSI(*MorphVertexFieldName), 3));
			Simplygon::spRealArray MorphNormalField = Simplygon::spRealArray::SafeCast(GeometryData->AddBaseTypeUserVertexField(Simplygon::EBaseTypes::TYPES_ID_REAL, TCHAR_TO_ANSI(*MorphNormalFieldName), 3));

			MorphVertexField->SetTupleCount(NumVertices);
			MorphNormalField->SetTupleCount(NumVertices);

			// Prepare wedge to morph vertex map for fast look up
			TArray<int32> VertexToMorphVert;
			VertexToMorphVert.AddUninitialized(NumVertices);
			for (uint32 Index = 0; Index < NumVertices; Index++)
			{
				VertexToMorphVert[Index] = -1;
			}
			for (int32 MorphVertIdx = 0; MorphVertIdx < NumDeltas; MorphVertIdx++)
			{
				const FMorphTargetDelta& MorphVertex = Deltas[MorphVertIdx];
				VertexToMorphVert[MorphVertex.SourceIdx] = MorphVertIdx;
			}

			// FVertexAnimDelta is sparse array structure holding information about vertices which are
			// changed by morph.
			for (uint32 VertexIndex = 0; VertexIndex < NumVertices; VertexIndex++)
			{
				int32 MorphVertIdx = VertexToMorphVert[VertexIndex];
				if (MorphVertIdx >= 0)
				{
					const FMorphTargetDelta& MorphVertex = Deltas[MorphVertIdx];
					check(MorphVertex.SourceIdx == VertexIndex);

					// Verify vertex index, just in case
					if (MorphVertex.SourceIdx < NumVertices)
					{
						// Store delta information into custom fields
						MorphVertexField->SetTuple(VertexIndex, (float*)&MorphVertex.PositionDelta);
						MorphNormalField->SetTuple(VertexIndex, (float*)&MorphVertex.TangentZDelta);
					}
				}
			}
		}
	}
}

void FSimplygonDataConverter::CreateMorphTargetsFromGeometry(
	TArray<UMorphTarget*>& MorphTargets,
	const Simplygon::spGeometryData GeometryData,
	const int32 LODIndex,
	const FSkeletalMeshLODModel* LODModel,
	const TArray<int32> VerticesToSkip,
	const TSet<TSoftObjectPtr<UMorphTarget>>* MorphTargetsToRemove)
{
	int32 NumVertices = LODModel->NumVertices;

	for (int32 MorphIndex = 0; MorphIndex < MorphTargets.Num(); MorphIndex++)
	{
		UMorphTarget* MorphTarget = MorphTargets[MorphIndex];

		if (MorphTarget)
		{
			if (MorphTargetsToRemove != nullptr && MorphTargetsToRemove->Contains(MorphTarget))
				continue;

			// Get vertex streams from GeometryData
			FString MorphVertexFieldName = FString::Printf(TEXT("MorphVertexField%d"), MorphIndex);
			FString MorphNormalFieldName = FString::Printf(TEXT("MorphNormalField%d"), MorphIndex);

			Simplygon::spRealArray MorphVertexField = Simplygon::spRealArray::SafeCast(GeometryData->GetUserVertexField(TCHAR_TO_ANSI(*MorphVertexFieldName)));
			Simplygon::spRealArray MorphNormalField = Simplygon::spRealArray::SafeCast(GeometryData->GetUserVertexField(TCHAR_TO_ANSI(*MorphNormalFieldName)));

			if (MorphVertexField.IsNull() || MorphNormalField.IsNull())
			{
				UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Cound not find following user vertex fields[%s,%s]. Currently morph targets are not taken into account when using the Aggregation/Remeshing pipelines."), *MorphVertexFieldName, *MorphNormalFieldName);
				continue;
			}

			TArray<FMorphTargetDelta> Deltas;
			Deltas.Reserve(NumVertices);

			// Read morph data from geometry.
			for (int32 VertexIndex = 0; VertexIndex < NumVertices; VertexIndex++)
			{
				// OriginalVertexIndex aka GeometryData vertex index
				int32 OriginalVertexIndex = LODModel->MeshToImportVertexMap[VertexIndex];
				if (VerticesToSkip.Contains(OriginalVertexIndex))
					continue;

				Deltas.Add(FMorphTargetDelta());
				FMorphTargetDelta& Delta = Deltas.Last();
				Delta.SourceIdx = VertexIndex;

				//Position delta
				Simplygon::spRealData MorphVertex = MorphVertexField->GetTuple(OriginalVertexIndex);
				const float* sgPositionDelta = MorphVertex.Data();
				Delta.PositionDelta = FVector(sgPositionDelta[0], sgPositionDelta[1], sgPositionDelta[2]);

				//Normal DeltaZ
				Simplygon::spRealData MorphNormal = MorphNormalField->GetTuple(OriginalVertexIndex);
				const float* sgNormalDeltaZ = MorphNormal.Data();
				Delta.TangentZDelta = FVector(sgNormalDeltaZ[0], sgNormalDeltaZ[1], sgNormalDeltaZ[2]);
			}

			MorphTarget->PopulateDeltas(Deltas, LODIndex, LODModel->Sections);
		}
	}
}

void FSimplygonDataConverter::RemoveAllMaterialChannels(Simplygon::spMaterial Material)
{
	int32 NumMaterialChannels = Material->GetMaterialChannelCount();
	TArray<Simplygon::spString> ChannelsToRemove;

	for (int32 ChannelIndex = 0; ChannelIndex < NumMaterialChannels; ++ChannelIndex)
	{
		ChannelsToRemove.Add(Material->GetMaterialChannelFromIndex(ChannelIndex));
	}

	for (Simplygon::spString ChannelName : ChannelsToRemove)
	{
		Material->RemoveMaterialChannel(ChannelName);
	}
}

void FSimplygonDataConverter::ConvertMaterialBakeOutput(const TArray<FBakeOutput>& BakeOutputs, const TArray<FMaterialData>& MaterialData, TArray<FFlattenMaterial>& FlattenedMaterials)
{
	for (int32 OutputIndex = 0; OutputIndex < BakeOutputs.Num(); ++OutputIndex)
	{
		const FBakeOutput& Output = BakeOutputs[OutputIndex];
		const FMaterialData& MaterialInfo = MaterialData[OutputIndex];

		FFlattenMaterial Material;

		for (TPair<EMaterialProperty, FIntPoint> SizePair : Output.PropertySizes)
		{
			EFlattenMaterialProperties FlattenProperty = MaterialPropertyToFlattenMaterialProperty(SizePair.Key);
			check(FlattenProperty != EFlattenMaterialProperties::NumFlattenMaterialProperties)

				Material.SetPropertySize(FlattenProperty, SizePair.Value);
			Material.GetPropertySamples(FlattenProperty).Append(Output.PropertyData[SizePair.Key]);
		}

		Material.bDitheredLODTransition = MaterialInfo.Material->IsDitheredLODTransition();
		Material.BlendMode = BLEND_Opaque;
		Material.bTwoSided = MaterialInfo.Material->IsTwoSided();
		Material.EmissiveScale = Output.EmissiveScale;

		FlattenedMaterials.Add(Material);
	}
}

EFlattenMaterialProperties FSimplygonDataConverter::MaterialPropertyToFlattenMaterialProperty(int32 NewProperty)
{
	const EFlattenMaterialProperties Remap[MP_Refraction] =
		{
			EFlattenMaterialProperties::Emissive,
			EFlattenMaterialProperties::Opacity,
			EFlattenMaterialProperties::OpacityMask,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::Diffuse,
			EFlattenMaterialProperties::Metallic,
			EFlattenMaterialProperties::Specular,
			EFlattenMaterialProperties::Roughness,
			EFlattenMaterialProperties::Anisotropy,
			EFlattenMaterialProperties::Normal,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::AmbientOcclusion};

	return Remap[NewProperty];
}

void FSimplygonDataConverter::ExtractSections(const USkeletalMesh* SkeletalMesh, int32 LODIndex, TArray<FSectionInfo>& OutSections)
{
	static UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
	FSkeletalMeshModel* Resource = SkeletalMesh->GetImportedModel();

	checkf(Resource->LODModels.IsValidIndex(LODIndex), TEXT("Invalid LOD Index"));

	/*TArray<FName> MaterialSlotNames = Component->GetMaterialSlotNames();*/

	const FSkeletalMeshLODInfo* LODInfo = SkeletalMesh->GetLODInfo(LODIndex);

	const FSkeletalMeshLODModel& Model = Resource->LODModels[LODIndex];
	for (const FSkelMeshSection& MeshSection : Model.Sections)
	{
		// Retrieve material for this section
		int32 MaterialIndex = MeshSection.MaterialIndex;
		if (LODInfo->LODMaterialMap.IsValidIndex(MaterialIndex) && SkeletalMesh->GetMaterials().IsValidIndex(LODInfo->LODMaterialMap[MaterialIndex]))
		{
			MaterialIndex = LODInfo->LODMaterialMap[MaterialIndex];
		}

		UMaterialInterface* StoredMaterial = SkeletalMesh->GetMaterials()[MaterialIndex].MaterialInterface;

		// Make sure the resource actual exists, otherwise use default material
		StoredMaterial = (StoredMaterial != nullptr) && StoredMaterial->GetMaterialResource(GMaxRHIFeatureLevel) ? StoredMaterial : DefaultMaterial;

		FSectionInfo SectionInfo;
		SectionInfo.Material = StoredMaterial;
		SectionInfo.MaterialSlotName = /*MaterialSlotNames.IsValidIndex(MeshSection.MaterialIndex) ? MaterialSlotNames[MeshSection.MaterialIndex] :*/ NAME_None;
		SectionInfo.MaterialIndex = MeshSection.MaterialIndex;

		if (MeshSection.bCastShadow /*&& Component->CastShadow*/)
		{
			SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FSkelMeshSection, bCastShadow));
		}

		if (MeshSection.bRecomputeTangent)
		{
			SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FSkelMeshSection, bRecomputeTangent));
		}

		OutSections.Add(SectionInfo);
	}
}

void FSimplygonDataConverter::ExtractMeshDescription(const USkeletalMesh* SkeletalMesh, int32 LODIndex, FMeshDescription* OutMeshDescription, bool bPropagateVertexColours)
{
	FSkeletalMeshModel* Resource = SkeletalMesh->GetImportedModel();
	if (Resource->LODModels.IsValidIndex(LODIndex))
	{
		const FSkeletalMeshLODInfo& SrcLODInfo = *(SkeletalMesh->GetLODInfo(LODIndex));

		FSkeletalMeshLODModel& LODModel = Resource->LODModels[LODIndex];

		TArray<FSoftSkinVertex> Vertices;
		LODModel.GetVertices(Vertices);

		const uint32 VertexCount = LODModel.NumVertices;
		const uint32 TexCoordCount = LODModel.NumTexCoords;
		check(TexCoordCount <= MAX_TEXCOORDS);

		const int32 NumSections = LODModel.Sections.Num();

		OutMeshDescription->Empty();

		int32 TotalTriangles = 0;
		int32 TotalCorners = 0;
		for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
		{
			const FSkelMeshSection& SkelMeshSection = LODModel.Sections[SectionIndex];
			TotalTriangles += SkelMeshSection.NumTriangles;
		}
		TotalCorners = TotalTriangles * 3;
		OutMeshDescription->ReserveNewVertices(VertexCount);
		OutMeshDescription->ReserveNewPolygons(TotalTriangles);
		OutMeshDescription->ReserveNewVertexInstances(TotalCorners);
		OutMeshDescription->ReserveNewEdges(TotalCorners);

		TVertexAttributesRef<FVector> VertexPositions = OutMeshDescription->VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
		TEdgeAttributesRef<bool> EdgeHardnesses = OutMeshDescription->EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
		TEdgeAttributesRef<float> EdgeCreaseSharpnesses = OutMeshDescription->EdgeAttributes().GetAttributesRef<float>(MeshAttribute::Edge::CreaseSharpness);
		TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = OutMeshDescription->PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
		TVertexInstanceAttributesRef<FVector> VertexInstanceNormals = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
		TVertexInstanceAttributesRef<FVector> VertexInstanceTangents = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
		TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
		TVertexInstanceAttributesRef<FVector4> VertexInstanceColors = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
		TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);

		// Copy skinned vertex positions
		uint32 FirstVertex = 0;
		for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex)
		{
			const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
			const uint32 LastVertex = FirstVertex + (uint32)Section.SoftVertices.Num();
			for (uint32 VertexIndex = FirstVertex; VertexIndex < LastVertex; ++VertexIndex)
			{
				FSoftSkinVertex& Vertex = Vertices[VertexIndex];
				const FVertexID VertexID = OutMeshDescription->CreateVertex();
				VertexPositions[VertexID] = Vertex.Position;
			}
			FirstVertex = LastVertex;
		}

		VertexInstanceUVs.SetNumIndices(MAX_TEXCOORDS);

		for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
		{
			const FSkelMeshSection& SkelMeshSection = LODModel.Sections[SectionIndex];
			const int32 NumWedges = SkelMeshSection.NumTriangles * 3;

			//Create the polygon group ID
			int32 SectionMaterialIndex = SkelMeshSection.MaterialIndex;
			int32 MaterialIndex = SectionMaterialIndex;
			// use the remapping of material indices for all LODs besides the base LOD
			if (LODIndex > 0 && SrcLODInfo.LODMaterialMap.IsValidIndex(SkelMeshSection.MaterialIndex))
			{
				MaterialIndex = FMath::Clamp<int32>(SrcLODInfo.LODMaterialMap[SkelMeshSection.MaterialIndex], 0, SkeletalMesh->GetMaterials().Num());
			}

			FName ImportedMaterialSlotName = SkeletalMesh->GetMaterials()[MaterialIndex].ImportedMaterialSlotName;
			const FPolygonGroupID SectionPolygonGroupID(SectionMaterialIndex);
			if (!OutMeshDescription->IsPolygonGroupValid(SectionPolygonGroupID))
			{
				OutMeshDescription->CreatePolygonGroupWithID(SectionPolygonGroupID);
				PolygonGroupImportedMaterialSlotNames[SectionPolygonGroupID] = ImportedMaterialSlotName;
			}
			int32 WedgeIndex = 0;
			for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < SkelMeshSection.NumTriangles; ++SectionTriangleIndex)
			{
				FVertexID VertexIndexes[3];
				TArray<FVertexInstanceID> VertexInstanceIDs;
				VertexInstanceIDs.SetNum(3);
				for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex, ++WedgeIndex)
				{
					const int32 VertexIndexForWedge = LODModel.IndexBuffer[SkelMeshSection.BaseIndex + WedgeIndex];
					VertexIndexes[CornerIndex] = FVertexID(VertexIndexForWedge);
					FVertexInstanceID VertexInstanceID = OutMeshDescription->CreateVertexInstance(VertexIndexes[CornerIndex]);
					VertexInstanceIDs[CornerIndex] = VertexInstanceID;

					const FSoftSkinVertex& SoftVertex = SkelMeshSection.SoftVertices[VertexIndexForWedge - SkelMeshSection.BaseVertexIndex];

					const FSoftSkinVertex& SkinnedVertex = Vertices[VertexIndexForWedge];
					const FVector TangentX = SkinnedVertex.TangentX;
					const FVector TangentZ = SkinnedVertex.TangentZ;
					const FVector4 UnpackedTangentZ = SkinnedVertex.TangentZ;
					const float TangentYSign = UnpackedTangentZ.W;

					VertexInstanceTangents[VertexInstanceID] = TangentX;
					VertexInstanceBinormalSigns[VertexInstanceID] = TangentYSign;
					VertexInstanceNormals[VertexInstanceID] = TangentZ;

					for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_TEXCOORDS; TexCoordIndex++)
					{
						//Add this vertex instance tex coord
						VertexInstanceUVs.Set(VertexInstanceID, TexCoordIndex, SoftVertex.UVs[TexCoordIndex]);
					}

					//Add this vertex instance color
					VertexInstanceColors[VertexInstanceID] = bPropagateVertexColours ? FVector4(FLinearColor(SoftVertex.Color)) : FVector4(1.0f, 1.0f, 1.0f);
				}
				//Create a polygon from this triangle
				const FPolygonID NewPolygonID = OutMeshDescription->CreatePolygon(SectionPolygonGroupID, VertexInstanceIDs);
				//Triangulate the polygon
				OutMeshDescription->ComputePolygonTriangulation(NewPolygonID);
			}
		}
	}
}

void FSimplygonDataConverter::OutputDebugGeometry(Simplygon::ISimplygon* SDK, Simplygon::spScene& Scene, const FString& OutputFileName)
{

	auto objexp = SDK->CreateSceneExporter();
	FString SimplygonPath = FPaths::ProjectIntermediateDir() + TEXT("Simplygon/");

	if (!FPaths::DirectoryExists(SimplygonPath))
	{
		IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
		PlatformFile.CreateDirectory(*SimplygonPath);
	}

	FString outputPath = FPaths::ConvertRelativePathToFull(SimplygonPath + OutputFileName);
	objexp->SetExportFilePath(TCHAR_TO_ANSI(*outputPath));
	//objexp->AddObserver(&EventHandler, Simplygon::Simplygon::EEventCodes::Progress);
	objexp->SetScene(Scene);
	objexp->Run();
}

void FSimplygonDataConverter::OutputDebugGeometry(Simplygon::ISimplygon* SDK, Simplygon::spGeometryData& Geometry, const FString& OutputFileName)
{

	auto objexp = SDK->CreateSceneExporter();
	auto Scene = SDK->CreateScene();
	auto SceneMesh = SDK->CreateSceneMesh();
	SceneMesh->SetGeometry(Geometry);
	Scene->GetRootNode()->AddChild(SceneMesh);
	FString SimplygonPath = FPaths::ProjectIntermediateDir() + TEXT("Simplygon/");

	if (!FPaths::DirectoryExists(SimplygonPath))
	{
		IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
		PlatformFile.CreateDirectory(*SimplygonPath);
	}

	FString outputPath = FPaths::ConvertRelativePathToFull(SimplygonPath + OutputFileName);
	objexp->SetExportFilePath(TCHAR_TO_ANSI(*outputPath));
	// objexp->AddObserver(&EventHandler, Simplygon::Simplygon::EEventCodes::Progress);
	objexp->SetScene(Scene);
	objexp->Run();
}

void FSimplygonDataConverter::SaveSimplygonSceneToIntermediate(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, const FString& Prefix, FGuid Id)
{
	check(SDK != nullptr);
	check(!Scene.IsNull());
	const USimplygonSettings* EditorSettings = GetDefault<USimplygonSettings>();
	const FString OutputDir = EditorSettings->DebugPath.Path;
	const FString OutputFileNameWithExtension = FString::Printf(TEXT("%s%s%s"), *Prefix, *Id.ToString().Left(8), TEXT(".sgscene"));
	const FString OutputFilePath = FPaths::Combine(OutputDir, *OutputFileNameWithExtension);
	if (FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*OutputDir))
	{
		auto FileNameAnsi = StringCast<ANSICHAR>(*OutputFilePath);
		Scene->SaveToFile(FileNameAnsi.Get());
	}
}

void FSimplygonDataConverter::SaveSimplygonPipelineToIntermediate(Simplygon::ISimplygon* SDK, Simplygon::spPipeline Pipeline, const FString& Prefix, FGuid Id)
{
	check(SDK != nullptr);
	check(Pipeline != nullptr);
	const USimplygonSettings* EditorSettings = GetDefault<USimplygonSettings>();
	const FString OutputDir = EditorSettings->DebugPath.Path;
	const FString OutputFilePath = FPaths::Combine(OutputDir, *FString::Printf(TEXT("%s%s%s"), *Prefix, *Id.ToString().Left(8), TEXT(".json")));
	if (FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*OutputDir))
	{
		Simplygon::spPipelineSerializer PipelineSerializer = SDK->CreatePipelineSerializer();
		auto FileNameAnsi = StringCast<ANSICHAR>(*OutputFilePath);
		PipelineSerializer->SavePipelineToFile(FileNameAnsi.Get(), Pipeline);
	}
}

float FSimplygonDataConverter::ConvertFromColorToWeights(float ColorIntensity, float Multiplier)
{
	// what we need to raise 2 to, to get multiplier squared
	float Exponent = FMath::Log2(Multiplier) * 2.0f;
	// get a value 1/multiplier to multiplier
	return FMath::Pow(2.0f, (ColorIntensity * Exponent)) / Multiplier;
}

FVector FSimplygonDataConverter::VectorToSimplygon(const FVector& vec)
{
	return FVector(vec.X, vec.Z, vec.Y);
}

FVector FSimplygonDataConverter::VectorFromSimplygon(const FVector& vec)
{
	return FVector(vec.X, vec.Z, vec.Y);
}

Simplygon::spSelectionSet FSimplygonDataConverter::FindOrCreateSelectionSet(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, const char* SelectionSetName)
{
	Simplygon::spSelectionSet SelectionSet = Scene->GetSelectionSetTable()->FindSelectionSet(SelectionSetName);
	if (SelectionSet.IsNull())
	{
		SelectionSet = SDK->CreateSelectionSet();
		SelectionSet->SetName(SelectionSetName);
		Scene->GetSelectionSetTable()->AddItem(SelectionSet);
	}
	return SelectionSet;
}

bool FSimplygonDataConverter::IsClippingPlaneEnabledInPipeline(Simplygon::spPipeline InPipeline)
{
	if (InPipeline->IsA("IRemeshingPipeline"))
	{
		return FSimplygonDataConverter::GetUseClippingPlaneSetting<Simplygon::spRemeshingPipeline>(InPipeline);
	}
	else if (InPipeline->IsA("IAggregationPipeline"))
	{
		return FSimplygonDataConverter::GetUseClippingPlaneSetting<Simplygon::spAggregationPipeline>(InPipeline);
	}
	return false;
}

void FSimplygonDataConverter::DisableClippingPlaneInPipeline(Simplygon::spPipeline InPipeline)
{
	if (InPipeline->IsA("IRemeshingPipeline"))
	{
		FSimplygonDataConverter::SetUseClippingPlaneSetting<Simplygon::spRemeshingPipeline>(InPipeline, false);
	}
	else if (InPipeline->IsA("IAggregationPipeline"))
	{
		FSimplygonDataConverter::SetUseClippingPlaneSetting<Simplygon::spAggregationPipeline>(InPipeline, false);
	}
}

void FSimplygonDataConverter::PopulateLandscapeCullingGeometry(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, Simplygon::spPipeline InPipeline, UWorld* World, const FBoxSphereBounds& EstimatedBounds)
{
	TArray<FMeshDescription*> CullingGeometries;

	FMeshMergeHelpers::RetrieveCullingLandscapeAndVolumes(World, EstimatedBounds, ELandscapeCullingPrecision::Medium, CullingGeometries);
	for (auto CullingGeoemtry : CullingGeometries)
	{
		FSimplygonDataConverter::AddToScene(SDK, Scene, InPipeline, CullingGeoemtry);
	}

	ParallelFor(CullingGeometries.Num(),
		[&CullingGeometries](int32 Index) {
			delete CullingGeometries[Index];
		});
}

///////////////////////////////////////////////////////////
//FSimplygonMeshMaterialInfo

inline FAddMeshMaterialInfoResult FSimplygonMeshMaterialInfo::Add(
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	const TArray<FSectionInfo>& Sections,
	const FMeshDescription* MeshDescription,
	const FString& MeshId)
{
	FAddMeshMaterialInfoResult Result;

	bBakeMaterials |= FlattenMaterialSettings.IsValid();

	for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex)
	{
		const FSectionInfo& Section = Sections[SectionIndex];

		if (bBakeMaterials)
		{
			FString MeshMaterialKey = FString::Printf(TEXT("%s_%d"), *MeshId, Section.Material->GetUniqueID());

			bool bMaterialVertexData = false;
			int32 NumTexCoords = 0;
			FMaterialUtilities::AnalyzeMaterial(Section.Material, FlattenMaterialSettings, NumTexCoords, bMaterialVertexData);

			if (bMaterialVertexData)
			{
				MeshMaterialKey += TEXT("_");
				MeshMaterialKey += FGuid::NewGuid().ToString();
			}

			// Store local id for later use to map custom texture coordinates to correct mesh settings
			Result.ComponentMeshMaterialKeys.AddUnique(MeshMaterialKey);

			int32 MaterialIndex = bUseUniqueSections ? MaterialOutputOrder.AddUnique(MeshMaterialKey) : MaterialOutputOrder.Add(MeshMaterialKey);
			Result.SectionIndexToMaterialId.Add(SectionIndex, MaterialIndex);
			Result.SlotNameToMaterialId.Add(Section.MaterialSlotName, MaterialIndex);
			Result.MaterialHasVertexData.Add(MaterialIndex, bMaterialVertexData);

			// In a mixed (baked and no baked materials) LOD chain we need a way to map
			// material indices to original sections.
			MaterialIndexToSlotName.FindOrAdd(MeshMaterialKey).Add(MaterialIndex, Section.MaterialSlotName);

			// Create mesh settings, needed for material baking (flatten material)
			if (!BakeMaterialMeshSettings.Contains(MeshMaterialKey))
			{
				FMeshData MeshSettings;
				// This material indices array corresponds to PolygonGroupID in the MeshDescription.
				// See how it is being used in FMeshMaterialRenderItem::PopulateWithMeshData()
				// We assume SectionIndex == PolygonGroupID
				MeshSettings.MaterialIndices.Add(SectionIndex);
				MeshSettings.RawMeshDescription = const_cast<FMeshDescription*>(MeshDescription);
				BakeMaterialMeshSettings.Add(MeshMaterialKey, MeshSettings);
			}

			// Create material settings, needed for material baking (flatten material)
			if (!BakeMaterialMaterialSettings.Contains(MeshMaterialKey))
			{
				FMaterialData MaterialSettings;
				MaterialSettings.Material = Section.Material;
				MaterialSettings.bPerformBorderSmear = true;
				MaterialSettings.PropertySizes = FlattenMaterialSettings.GetMaterialPropertySizes();
				BakeMaterialMaterialSettings.Add(MeshMaterialKey, MaterialSettings);
			}
		}
		else
		{
			FString MaterialKey = FString::Printf(TEXT("%d"), Section.Material->GetUniqueID());
			int32 MaterialIndex = bUseUniqueSections ? MaterialOutputOrder.AddUnique(MaterialKey) : MaterialOutputOrder.Add(MaterialKey);
			Result.SectionIndexToMaterialId.Add(SectionIndex, MaterialIndex);
			Result.SlotNameToMaterialId.Add(Section.MaterialSlotName, MaterialIndex);
			MaterialIndexToSlotName.FindOrAdd(MaterialKey).Add(MaterialIndex, Section.MaterialSlotName);

			if (!SourceMaterials.Contains(MaterialKey))
			{
				SourceMaterials.Add(MaterialKey, Section.Material);
			}
		}
	}
	return Result;
}

inline void FSimplygonDataConverter::UpdateVectorsInPipeline(Simplygon::spPipeline InPipeline)
{
	auto ImposterPipeline = Simplygon::spImpostorFromSingleViewPipeline::SafeCast(InPipeline);
	if (!ImposterPipeline.IsNull())
	{
		// NOTE: We need to chagne the frame for view vector before running processing as we change the frame for the geometry.
		auto ViewDirection = ImposterPipeline->GetImpostorFromSingleViewSettings()->GetViewDirection();
		FVector ViewDirectionCorretedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector(ViewDirection.Data()[0], ViewDirection.Data()[1], ViewDirection.Data()[2]));
		ImposterPipeline->GetImpostorFromSingleViewSettings()->SetViewDirection((float*)&ViewDirectionCorretedFrame);
	}

	auto FlipbookPipeline = Simplygon::spFlipbookPipeline::SafeCast(InPipeline);
	if (!FlipbookPipeline.IsNull())
	{
		// We need to chagne the frame for up and view vectors before running processing as we change the frame for the geometry.
		auto ViewDirection = FlipbookPipeline->GetFlipbookSettings()->GetViewDirection();
		FVector ViewDirectionCorretedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector(ViewDirection.Data()[0], ViewDirection.Data()[1], ViewDirection.Data()[2]));
		FlipbookPipeline->GetFlipbookSettings()->SetViewDirection((float*)&ViewDirectionCorretedFrame);

		auto UpVector = FlipbookPipeline->GetFlipbookSettings()->GetUpVector();
		FVector UpVectorCorrectedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector(UpVector.Data()[0], UpVector.Data()[1], UpVector.Data()[2]));
		FlipbookPipeline->GetFlipbookSettings()->SetUpVector((float*)&UpVectorCorrectedFrame);
	}

	auto BillboardCloudOuterShell = Simplygon::spBillboardCloudPipeline::SafeCast(InPipeline);
	if (!BillboardCloudOuterShell.IsNull())
	{
		auto UpVector = BillboardCloudOuterShell->GetBillboardCloudSettings()->GetUpVector();
		FVector UpVectorCorrectedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector(UpVector.Data()[0], UpVector.Data()[1], UpVector.Data()[2]));
		BillboardCloudOuterShell->GetBillboardCloudSettings()->SetUpVector((float*)&UpVectorCorrectedFrame);
	}

	auto BillboardCloudFoliage = Simplygon::spBillboardCloudVegetationPipeline::SafeCast(InPipeline);
	if (!BillboardCloudFoliage.IsNull())
	{
		auto UpVector = BillboardCloudFoliage->GetBillboardCloudSettings()->GetUpVector();
		FVector UpVectorCorrectedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector(UpVector.Data()[0], UpVector.Data()[1], UpVector.Data()[2]));
		BillboardCloudFoliage->GetBillboardCloudSettings()->SetUpVector((float*)&UpVectorCorrectedFrame);
	}
}

FString FSimplygonDataConverter::GetPipelineBakedMaterialBaseTemplatePath(const Simplygon::spPipeline InPipeline, uint32 InIndex)
{
	bool IsCascaded = InPipeline->GetCascadedPipelineCount() > 0;

	if (IsCascaded)
	{
		if (IsCascadedPipelineOfType<Simplygon::spFlipbookPipeline>(InPipeline, InIndex))
			return TEXT("/Simplygon/Materials/M_SG_BaseFlipbook.M_SG_BaseFlipbook");

		if (IsCascadedPipelineOfType<Simplygon::spBillboardCloudVegetationPipeline>(InPipeline, InIndex) ||
			IsCascadedPipelineOfType<Simplygon::spBillboardCloudPipeline>(InPipeline, InIndex))
			return TEXT("/Simplygon/Materials/M_SG_BaseImposter.M_SG_BaseImposter");
	}
	else
	{
		if (IsPipelineOfType<Simplygon::spFlipbookPipeline>(InPipeline))
			return TEXT("/Simplygon/Materials/M_SG_BaseFlipbook.M_SG_BaseFlipbook");

		if (IsPipelineOfType<Simplygon::spBillboardCloudVegetationPipeline>(InPipeline) ||
			IsPipelineOfType<Simplygon::spBillboardCloudPipeline>(InPipeline))
			return TEXT("/Simplygon/Materials/M_SG_BaseImposter.M_SG_BaseImposter");
	}

	return TEXT("/Engine/EngineMaterials/BaseFlattenMaterial.BaseFlattenMaterial");
}

FBoxSphereBounds FSimplygonDataConverter::CalculateBounds(const TArray<FVector>& InVertices)
{
	FBoxSphereBounds Result;
	FBox BoundingBox;
	BoundingBox.Init();

	for (const auto& Vertex : InVertices)
	{
		BoundingBox += FVector(Vertex);
	}

	BoundingBox.GetCenterAndExtents(Result.Origin, Result.BoxExtent);
	Result.SphereRadius = 0.0f;

	for (const auto& Vertex : InVertices)
	{

		Result.SphereRadius = FMath::Max((FVector(Vertex) - Result.Origin).Size(), Result.SphereRadius);
	}

	return Result;
}

#undef LOCTEXT_NAMESPACE
